@modelprofile.com/browser-runtime
Parent-owned, resource-centric Chromium runtime with revisioned attachment fencing, authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.
Selected-tab DevTools
Set the trusted host option devTools: true to enable humanLease.openDevTools({ tabId, onMessage, onClose }, operationOptions?). Agent leases cannot acquire an
inspector. The returned connection exposes tabId, browserVersion,
send(message), and close() using SmartPuppeteer's bounded CDP contract.
DevTools commands run concurrently on a separate lane, so pausing JavaScript does
not queue Debugger.resume behind a pending evaluation. The lane reserves interrupt
capacity and applies the normal authorization hook and audit policy to every command;
audit records contain the method and outcome, never expressions or response bodies.
Lease, attachment, and browser incarnation authority are rechecked before execution
and around every delivered event. Revocation closes pending and late-opening
attachments. Failed cleanup terminates the exact owned browser incarnation.
Await each onMessage delivery to preserve transport backpressure. Closing an
inspector does not close the human lease or its video peer. The embedding application
must close the inspector when changing the inspected tab or replacing its transport.
No browser debugging port or browser-wide target discovery is exposed. Pair this
connection with DevToolsFrontend from @push.rocks/smartbrowser/web for the official
Chrome DevTools UI.
Page dialogs
state.tabs[].dialog exposes the exact pending website dialog. A human lease can call respondToDialog({ tabId, dialogId, accept, promptText? }, options?). This response receives the same authorization, audit, cancellation, and incarnation checks as other operations while bypassing document serialization. Agent leases cannot send this human response. A pending dialog rejects raw input with DIALOG_PENDING, preserving held-input tracking until it closes. Before input enters a new document, the runtime releases native keys and buttons retained from the previous document. Obsolete input and dialog identities fail with STALE_INPUT; they do not imply a failed browser connection.
Native video viewers
Human leases expose openVideoPeer(options?), answerVideoPeer(negotiationId, description, options?), closeVideoPeer(options?), and getVideoStatistics(options?). Each operation retains bounded authorization, auditing and cancellation. Statistics use an independent observation slot so a slow telemetry read cannot block input or media lifecycle work. Peer IDs are private to the lease; offers expose the negotiation ID and exact tab/generation/viewport source identity. Agent leases cannot open media peers. Releasing or revoking a human lease closes only its peer, preserving other viewers and agent work. Failed peer cleanup remains owned for a subsequent release attempt.
Set video on BrowserRuntime for trusted host limits or ICE configuration. Defaults use direct connections with no external STUN/TURN service and automatic GPU support. The private capture extension is loaded without weakening ordinary page proxy, DNS, permission or WebRTC confinement. state.videoAcceleration and peer statistics report actual browser acceleration/encoder support.
video: { backend: 'native', maxFrameRate: 30 } selects SmartPuppeteer's Rust/NVIDIA
capture and NVENC sender. Chromium remains the default backend. Native viewers must
forward state.videoSource unchanged, including its RTP presentation fence and coded
frame alignment. The native viewport negotiates device scale 1 and fits the configured
capture limits while preserving the preferred aspect ratio. The returned viewport owns
input coordinates. Unsupported receiver codec limits fail negotiation explicitly.
Forward DOM pointer event.timeStamp as dispatchMouse().timestampMs to preserve the
occurrence spacing of a gesture delivered in a transport burst. Participant changes reset
the native timing history because viewer clocks have different origins; synthesized
button releases omit timestamps. Existing operation ordering and held-button ownership
remain authoritative.
Native viewers should call lease.subscribeEvents(listener, { includeFrames: false }). They receive state and errors without generating JPEG traffic. Existing image consumers retain the default includeFrames: true; capture starts for the first such subscriber and stops after the last closes. Agent snapshots remain independent of continuous capture. Image subscription cleanup is serialized, coalesced and retryable; a failed disable is retained until cleanup succeeds or its exact browser incarnation terminates.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Install
pnpm add @modelprofile.com/browser-runtime
The runtime requires Node.js 24 through 26 on a non-root Linux x64 host. Production browser sessions require a sandbox-capable Chromium installation.
The NamedMutex backend uses the statically linked Rust helper packaged with @push.rocks/smartipc; startup fails closed with FENCED when the helper is unavailable.
Resource Model
The Controller owns durable resource and attachment truth. BrowserRuntime owns process-local registrations, fences, capabilities, leases, browser incarnations, artifacts, and its host-runtime ownership lease. Registration never launches Chromium.
import {
BrowserRuntime,
type TBrowserCapabilityAuthorizationRequest,
} from '@modelprofile.com/browser-runtime';
const runtime = new BrowserRuntime({
runtimeDirectory: '/var/lib/example/browser-runtime',
authorizeCapability: async (binding) => hostPolicy.authorizeBrowser(binding),
beforeOperation: async (operation) => hostAudit.recordAttempt(operation),
});
try {
await runtime.start();
const resource = runtime.createResource({
projectId: 'project-123',
attachmentBinding: {
attachmentAuthorityId: 'controller-attachment-1',
attachmentRevision: 1,
sessionIds: [{ harnessId: 'opencode', nativeId: 'session-456' }],
},
});
const binding = {
projectId: resource.projectId,
browserResourceId: resource.browserResourceId,
attachmentAuthorityId: resource.attachmentBinding.attachmentAuthorityId,
attachmentRevision: resource.attachmentBinding.attachmentRevision,
sessionId: { harnessId: 'opencode', nativeId: 'session-456' },
actorId: 'agent-456',
role: 'agent',
peerId: 'worker-789',
source: 'mcp',
} satisfies TBrowserCapabilityAuthorizationRequest;
const issued = await runtime.issueCapability(binding);
const lease = await runtime.acquireLease({
...binding,
capabilityToken: issued.capabilityToken,
});
try {
console.log(await lease.executeAgentAction({ action: 'snapshot' }));
} finally {
await lease.release();
}
} finally {
await runtime.stop();
}
Projects and qualified OpenCode, Flex, Codex, and Claude sessions may each own many resources. Agent capabilities use Flex's trusted pipe or an independently authenticated MCP binding for any supported native session. Each resource has one browser incarnation and operation queue shared by its attached agent and multiple human viewers. Joining a viewer never preempts another participant.
registerResource() is idempotent only for the same project/resource key and identical attachment. listResources() reports process-local registration and incarnation metadata. terminateResource() terminates only the current incarnation and preserves registration, attachment, and artifacts. retireResource() permanently fences the process-local registration, revokes and quiesces authority, terminates its incarnation, purges exact-resource artifacts, and unregisters only after cleanup succeeds. The Controller separately owns durable retirement truth and must not rehydrate retired resources. Runtime tombstones and registrations are ephemeral, bounded process state. stop() attempts peer, capability, resource, artifact-store, and ownership cleanup even when an earlier stage fails, then reports all failures together. It removes only the current generation, durably marks its owner metadata relinquished, and releases the native ownership lease when ownership cleanup succeeds. It intentionally leaves runtime.lock and the mutex anchor in place. Failed local slot cleanup remains retryable and blocks a new generation; automatic ownership-loss shutdown performs one additional bounded cleanup attempt. cleanupTimeoutMs defaults to 30,000 milliseconds and accepts values from 100 through 120,000. If that caller-visible deadline expires, stop() rejects with TIMEOUT while Runtime retains cleanup still in flight; a later stop() rejoins it, and start() waits for all prior local cleanup before restarting.
Host Runtime Ownership
The runtime directory must be an owner-only 0700 directory on a trusted local filesystem; NFS and other network filesystems are unsupported. Its permanent runtime.mutex anchor directory is an owner-only 0700 directory on the same filesystem device. Runtime uses @push.rocks/smartipc NamedMutex with a namespace bound to the UID plus the validated runtime-directory device, inode, and path hash. SmartIPC keeps one permanent 0600, single-link anchor file in runtime.mutex and holds a native advisory lock for the complete metadata and generation operation. Clean tryAcquire() contention returns LOCKED. Unsupported native backends, unsafe or changed anchors, native lock failures, and uncertain release return FENCED. Process exit, SIGKILL, and reboot release the advisory lock in the kernel.
runtime.lock is a permanent 0600, single-link regular file retained through an open descriptor while ownership is live. It is metadata and a downgrade fence, not the live mutex, and is never removed by stop(). Version 3.2 and older create-exclusive runtimes therefore remain fenced during and between new-runtime generations. Bounded JSON metadata is overwritten and fsynced through that descriptor. It records the schema, directory identity, UID, boot ID, PID, /proc/self/stat start ticks, generation ID, nonce, and active/relinquished state. Malformed metadata or unsafe directory, anchor, lock, link, ownership, mode, containment, or inspection state fails closed with FENCED.
Each successful start() creates private generations/<generationId>/profiles and generations/<generationId>/artifacts directories. A replacement holding the native mutex may inspect and remove only the generation named by valid metadata. For metadata generations, Runtime does not scan runtime.lock descriptors. It inspects every candidate process and returns LOCKED when any process, Chromium or otherwise, has an absolute --user-data-dir equal to or below that generation's profile root. Unknown generations and indeterminate inspection remain fenced.
The inspection exists only to protect a profile root that is about to be removed, so when no inspected profile root is present the process table is never enumerated; an unreadable profile root is FENCED. Process identity is classified from all four IDs in /proc/<pid>/status Uid:. Same-UID processes and shared-UID processes — an ordinary setuid credential transition such as sudo, su or passwd, where this UID appears in some but not all four IDs — are both candidates; definite other-UID processes are ignored. A shared UID is never a fence by itself, and a single such process anywhere in /proc must never fence startup: the candidate's command line decides it. A readable command line naming no --user-data-dir at or below an inspected root proves the process is not using it. A candidate whose command line cannot be read is fail-closed and remains indeterminate, as does unparseable Uid: state.
An empty command line is not a decision either. A process caught mid-execve or past exit_mm publishes one for a bounded window while State: still reports it running, so Runtime re-reads the identity and the command line over that window — five attempts 50 ms apart, at most 200 ms of waiting per candidate — before deciding. A departed PID, a definite other UID, and a zombie of any candidate UID state are clear; a command line that appears within the window takes the ordinary containment path and can still return LOCKED; a candidate still running and still command-less after the window is FENCED.
A version 3.2 zero-byte lock uses a separate one-time migration gate. Runtime adopts it only when SmartIPC's consuming kernel exclusivity probe reports no other read/write open description, the lock's birth, change, and modification timestamps are all strictly before the bounded /proc/stat btime, and, when that root is present, no candidate process has --user-data-dir equal to or below the legacy profiles root. The exact original inode, ownership, mode, link count, zero size, and timestamps are revalidated when the lock is reopened after probing. Probe contention is LOCKED; probe or reopen uncertainty is FENCED. O_PATH descriptors do not contend. A same-boot unheld lock is FENCED; this prevents takeover during the old owner's close-before-unlink window. Malformed boot data or ambiguous timestamps are also FENCED.
When version 3.2 stopped cleanly and removed runtime.lock, it may leave empty top-level profiles and artifacts directories. After winning creation of a new lock under the native mutex, Runtime validates those directories as exact private empty roots, performs the same exact-or-descendant profile-process inspection, removes only the validated roots, and publishes the new metadata without applying the stale-lock boot gate. If startup fails before metadata is durably published, Runtime unlinks only that exact process-created lock inode while still holding the native mutex; a preexisting lock is never removed.
Breaking Changes
Shared control replaces exclusive human takeover. Hosts must allow multiple exact participant leases, stop treating chat attachment changes as human-view revocation, and consume the effective viewport returned by setViewport(). Browser destruction remains an explicit resource operation.
The version 3.2 runtime-directory migration is one-way. A cleanly stopped 3.2 runtime that removed its zero-byte lock can be upgraded directly when any remaining top-level profiles and artifacts directories are private and empty. A stale 3.2 lock is recoverable only after reboot, once it conclusively predates the current boot and no legacy holder or profile process remains. The first successful new-runtime start writes permanent metadata and creates runtime.mutex; version 3.2 must not reuse that directory.
To downgrade, first stop the new runtime and verify that no process still uses any generation profile path. Then remove the entire runtime directory, including runtime.lock, runtime.mutex, and generations, and let version 3.2 create a fresh directory. Never delete only the lock or anchor, and never perform this procedure while either runtime generation is active.
Attachment Fencing
Attachment bindings are Controller-owned { attachmentAuthorityId, attachmentRevision, sessionIds } values. sessionIds is the set of sessions attached to the resource, so one browser resource can serve several conversations at once. Order is not significant, duplicate members are rejected with INVALID_INPUT, and a set larger than 64 members is rejected with QUOTA_EXCEEDED. An empty set is detached; revision 0 is the initial detached/no-agent-authority state and requires an empty set. Reapplying the identical revision and set is idempotent, including in a different order; lower revisions and conflicting equal revisions fail.
A session is qualified: harnessId names the conversation namespace its nativeId belongs to, so the same nativeId in two namespaces is two different members.
harnessId |
Conversation | Capability sources |
|---|---|---|
opencode |
An OpenCode session | mcp |
flex |
A Flex run | flex trusted pipe, mcp |
codex |
A Codex thread | mcp |
claude |
A Claude Code conversation, including the one that owns a terminal an agent runs in | mcp |
Every kind takes a non-empty nativeId of at most 256 characters and participates identically in the attachment set, the per-session fence, selective rebind, de-duplication, ordering and the 64-member bound. The flex capability source and the trusted framed transport remain Flex-only: any other kind presented on the flex source is rejected with INVALID_INPUT.
await runtime.applyAttachmentBinding({
projectId: resource.projectId,
browserResourceId: resource.browserResourceId,
attachmentBinding: {
attachmentAuthorityId: 'controller-attachment-1',
attachmentRevision: 2,
sessionIds: [],
},
});
A newer binding fences only the sessions that left the set. Their leases and in-flight operations are aborted with CAPABILITY_REVOKED naming the session, their framed channels are closed, and their agent capabilities are revoked. Sessions that stay in the set keep their leases, framed channels and capabilities: nothing of theirs is aborted, closed or revoked, so once the rebind completes they carry on exactly as before and adding a session never interrupts the sessions already attached. While the rebind is still fencing, a session that is still in the set is never revoked: both issuing a new capability and operating an existing lease fail with BUSY, which is retryable, and the same call succeeds once the rebind settles. From the moment the rebind commits the new set, a session it removed is told it is gone — CAPABILITY_REVOKED — for the rest of that rebind, so the two outcomes are never confused. Before that commit the removal has not happened yet and may still not happen, so a session the queued rebind will remove also reports BUSY and should retry. Detaching with an empty set therefore releases every attached session. Human viewers retain their resource authority and subscriptions. The incarnation is preserved unless an operation cannot quiesce; that failure requires terminating the resource's browser process.
The revision fence is per session: a capability survives exactly as long as its session stays continuously attached; leaving and rejoining invalidates the old ones. Concretely, a capability is admitted while its session is a current member and the capability's attachmentRevision is at least the revision at which that session joined the set and no newer than the current revision. A session that leaves and later rejoins gets the newer join revision, so every capability it held before it left is rejected. Human capabilities carry no session and keep the exact-revision fence.
Agent capabilities require the exact current non-detached qualified session. Human capabilities carry no session ID and may be issued while detached. Issuance validates the current attachment binding, but an issued human capability remains valid across attachment changes: hosts must authorize it against its project, resource, actor and peer rather than the resource's later agent assignment.
Capabilities expire after five minutes by default. A host keeping a viewer open can call
await lease.renew({ signal }) before lease.expiresAt; the result is the new expiry
time in Unix milliseconds. Renewal invokes authorizeCapability again and preserves
the exact lease, browser incarnation, frame subscription and attachment identity.
expiresInMs optionally selects a lifetime within the configured maximum. An expired,
released, revoked or replaced lease cannot be renewed. A denied, cancelled or timed-out
renewal leaves the original expiry unchanged. Only one authorization callback per lease
may remain unsettled, including after timeout; runtime-wide renewal admission is bounded
by maxCapabilities. Hosts must stop scheduling renewal when their viewer disconnects.
Agent actions are exactly navigate, snapshot, screenshot, click, fill, and press. Agents can read or delete screenshots created by their exact lease using readArtifact() and deleteArtifact(). Human leases additionally expose tab lifecycle, viewport, raw input, frame subscription/acknowledgement and refresh, and exact-resource artifact reads/deletes. JavaScript evaluation is not public.
BrowserRuntime alone owns the bounded execution scheduler shared by all participants. Host transports submit operations in order without adding a second execution queue or performing asynchronous authorization ahead of admission. One statistics read may run independently of mutations; its preflight and completion cannot fence input, navigation, or dialog replies. Up to four wheel operations may run concurrently, with authorization and native-send admission kept in order. Other input, viewport, and semantic operations remain barriers behind preceding wheel work. Dialog replies use one interrupt slot so a paused document operation cannot block its own response. maxQueuedOperationsPerLease defaults to 128 and accepts 1 through 1,024; each participant has that bound, and the resource queue is bounded by its product with maxCapabilitiesPerResource. Overflow fails with QUOTA_EXCEEDED. Releasing a participant cancels only its queued and active work. Resource termination and shutdown cancel everyone. Started uncertain work must quiesce or the exact incarnation is terminated before operation settlement.
Interactive hosts can pass { coalesceHoverMoves: true } to humanLease.dispatchMouse(). Only an adjacent, unstarted, unpressed move from the same lease, document, viewport and modifier state is replaced. The replaced promise rejects with BrowserRuntimeError code SUPERSEDED; hosts should report that terminal outcome without treating it as a failed connection. Drag paths, queued or held buttons, other participants, and discrete operations retain their order. The default preserves every submitted operation.
setViewport() stores the viewer's preferred width, height and device scale factor and returns IBrowserRuntimeViewportResult with the effective viewport and viewportRevision. The effective values are the componentwise minima across active viewers that have supplied a preference. Unchanged effective sizes do not restart the stream. Removing a viewer recomputes the viewport in the resource FIFO. Held keys and buttons are tracked per participant; leaving releases only inputs that no other participant holds. Viewport and tab/navigation transitions clear held input before changing the target. Resource-owned departure cleanup is bounded and audited as releaseParticipant; it does not call the external beforeOperation gate after the participant has lost access.
beforeOperation is an optional awaited fail-closed gate. It receives the complete immutable authority, operation/capability/lease IDs, action, classification, start time, and an AbortSignal after dequeue but before the browser side effect starts. Classifications are raw-input, dialog, devtools, frame-stream, video-peer, viewport, navigation, tab, and agent-action. Hosts that require durable attempt-before-side-effect auditing should persist the attempt there. Rejection denies the operation. The default beforeOperationTimeoutMs is 10,000 milliseconds and accepts values from 100 through 120,000; timeout fails with TIMEOUT. The terminal audit callback remains a best-effort completed/failed notification correlated by the same operation ID and classification and runs after bounded operation cleanup releases or fences the exact reservation.
lease.getAuthority() returns an immutable process-local lease authority containing the runtime authority ID, exported authorityGeneration, incarnation generation, and complete capability binding. Every snapshot also binds the exact capabilityId and leaseId. lease.isAuthorityCurrent(authority) performs an exact synchronous revalidation suitable for a host-owned virtual stream. These values are not durable Controller state and do not replace attachment checks against the Controller database.
Each human participant may have one independent frame subscription with a bounded exact-identity window. maxOutstandingFrames defaults to 4 and accepts 1 through 32. Runtime starts exactly one bounded producer acknowledgement per admitted frame and shares its outcome between subscribers. Viewer acknowledgements and oldest-first eviction retire only that viewer's entry; they never acknowledge the producer a second time. A producer result of accepted: false is a settled result, while rejection, timeout or producer admission overflow fences the failed incarnation. A throwing viewer listener revokes only that participant.
frameAcknowledgementTimeoutMs defaults to 10,000 milliseconds and accepts 100 through 60,000. It bounds producer acknowledgements and each viewer's local window lifetime independently. A late viewer acknowledgement returns false once its entry has expired; it does not stop the browser, revoke another participant, or delay producer delivery.
An oversized frame is dropped, not fatal. maxFrameBytes defaults to 4 MiB and accepts values from 64 KiB through 16 MiB. A frame above the bound is acknowledged toward SmartPuppeteer in the background, never enters the application window, and is reported once on the subscription as { type: 'error', error: { code: 'FRAME_TOO_LARGE', fatal: false, tabId } }. The sequence watermark advances past the dropped frame so later frames remain valid and continue to be delivered.
screencast tunes the Chromium image capture that starts when an image-frame subscriber joins. quality is the JPEG quality from 0 through 100 and defaults to 70. maxWidth and maxHeight accept values from 1 through 4,096 each, their product must not exceed 8,294,400 pixels (SmartPuppeteer's ceiling, so 3,840×2,160 is accepted and 4,096×4,096 is rejected with INVALID_INPUT), they default to 2,560 and 1,600, and they make Chromium scale captured frames to fit. everyNthFrame accepts values from 1 through 60 and defaults to 1. firstFrameTimeoutMs accepts values from 1,000 through 60,000 and is left to SmartPuppeteer's default when omitted. Runtime forwards these values together with maxOutstandingFrames; the defaults keep frames from device-pixel-ratio 2 viewers well below maxFrameBytes.
Human state and error events retain an optional diagnostic message bounded to 2,048 characters. A fatal producer error synchronously invalidates all current participants before their captured subscribers receive the error. Cleanup is coalesced for that exact incarnation and completes before affected operations settle.
humanLease.refreshFrameStream(options?: IBrowserRuntimeOperationOptions): Promise<void> requires that participant's current subscription and enters the shared audited FIFO. It works for a running, open active tab even when its stream is invalidated. Runtime clears old viewer windows, waits for producer acknowledgement work, and validates the returned new-generation boundary against the current tab, viewport, session and incarnation. Every viewer receives the new stream. Missing or replaced subscriptions fail with BUSY; an abandoned initiator returns ABORTED without removing other viewers. A broken producer or invalid refresh boundary fails with FRAME_STREAM_FAILED or FRAME_TOO_LARGE after exact-incarnation cleanup. Caller cancellation preserves a successfully restored stream.
Trusted Pipe And Flex
Trusted framed peers and clients receive the complete authority out of band: project, resource, attachment authority/revision, actor, peer, role, source, qualified session, Flex scope, exact run, and resource-specific channel. Incoming frames cannot select identity. One session may use multiple resource-specific channels concurrently. Framed client/server request work and queued writes are bounded under backpressure.
BrowserRuntimeFlexToolProvider<TScope> resolves only a capability token and exposes the six approved SmartAgent actions. The provider requires its client run ID to match the exact Flex tool-provider context, and every runtime operation hook retains that run ID. The framed server validates the token against its complete trusted binding.
MCP Handler
createBrowserRuntimeMcpHttpHandler() requires independent request authentication to return the complete expected MCP binding. The bearer capability must match it exactly. Identity remains server-owned and no tool input contains a project, resource, authority, revision, session, actor, or peer selector.
The MCP tool list is exactly browser_navigate, browser_snapshot, browser_screenshot, browser_click, browser_fill, and browser_press.
Egress And Artifacts
Each running resource owns one authenticated loopback BrowserEgressProxy carrying immutable projectId and browserResourceId. HTTP, WebSocket Upgrade, and CONNECT share strict public-unicast DNS/IP validation, numeric dialing, bounded lifetimes, and fail-closed policy.
Artifact identity and APIs use (projectId, browserResourceId, artifactId). Keyed project/resource directories prevent caller IDs from entering paths. Admission is serialized across per-resource, per-project, and global count/byte quotas. Reads use no-follow handles and verify size and SHA-256. Screenshot actions return metadata. Direct agent leases can read/delete only artifacts created by that exact lease; other agent and human artifacts remain unavailable. Human artifact access remains scoped to the resource. Read completion revalidates the lease and clears bytes if authority ended during IO. The built-in framed/Flex/MCP action adapters continue to return metadata; hosts can return image content after a scoped lease read. The artifact store accepts an optional opaque owner ID on store() and an expected owner ID on read()/delete(); omitting an expected owner retains its trusted parent API.
Verification
pnpm install
pnpm dedupe
pnpm run build
pnpm run check:test
pnpm test
pnpm run test:real-chrome
The real Chromium check launches two resources in one project and qualified session, verifies distinct generation-scoped private profiles, sandboxed renderer process trees, mandatory confinement, screenshots, confirmed shutdown, and generation deletion.
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the contents of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.