diff --git a/website/.astro/content-modules.mjs b/website/.astro/content-modules.mjs index 9bec88e58e..0cb7cf1d4f 100644 --- a/website/.astro/content-modules.mjs +++ b/website/.astro/content-modules.mjs @@ -4,16 +4,17 @@ export default new Map([ ["src/content/docs/docs/approvals.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fapprovals.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/authentication.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fauthentication.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/architecture.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Farchitecture.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/benchmarks.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fbenchmarks.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/bindings.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fbindings.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/core.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcore.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/browser.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fbrowser.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/benchmarks.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fbenchmarks.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/cost-evaluation.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcost-evaluation.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/crash-course.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcrash-course.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/core.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcore.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/cron.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcron.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/debugging.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fdebugging.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/deployment.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fdeployment.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/filesystem.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Ffilesystem.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/index.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Findex.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/deployment.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fdeployment.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/debugging.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fdebugging.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/js-runtime.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fjs-runtime.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/limitations.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Flimitations.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/llm-credentials.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fllm-credentials.mdx&astroContentModuleFlag=true")], @@ -31,13 +32,13 @@ export default new Map([ ["src/content/docs/docs/security-model.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fsecurity-model.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/sessions.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fsessions.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/software.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fsoftware.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/system-prompt.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fsystem-prompt.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/versus-sandbox.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fversus-sandbox.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/webhooks.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fwebhooks.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/system-prompt.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fsystem-prompt.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/agents/codex.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Fcodex.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/workflows.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fworkflows.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/agents/claude.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Fclaude.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/agents/codex.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Fcodex.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/agents/custom.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Fcustom.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/workflows.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fworkflows.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/agents/opencode.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Fopencode.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/agents/pi.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fagents%2Fpi.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/architecture/agent-sdk-snapshots.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Farchitecture%2Fagent-sdk-snapshots.mdx&astroContentModuleFlag=true")], @@ -49,8 +50,8 @@ export default new Map([ ["src/content/docs/docs/architecture/packages-and-command-resolution.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Farchitecture%2Fpackages-and-command-resolution.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/architecture/posix-syscalls.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Farchitecture%2Fposix-syscalls.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/architecture/processes.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Farchitecture%2Fprocesses.mdx&astroContentModuleFlag=true")], -["src/content/docs/docs/architecture/sessions-persistence.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Farchitecture%2Fsessions-persistence.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/custom-software/building-wasm.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcustom-software%2Fbuilding-wasm.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/custom-software/definition.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcustom-software%2Fdefinition.mdx&astroContentModuleFlag=true")], +["src/content/docs/docs/architecture/sessions-persistence.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Farchitecture%2Fsessions-persistence.mdx&astroContentModuleFlag=true")], ["src/content/docs/docs/custom-software/publishing.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdocs%2Fcustom-software%2Fpublishing.mdx&astroContentModuleFlag=true")]]); \ No newline at end of file diff --git a/website/.astro/data-store.json b/website/.astro/data-store.json index 1b2cfea957..bb9702a6fc 100644 --- a/website/.astro/data-store.json +++ b/website/.astro/data-store.json @@ -1 +1 @@ -[["Map",1,2,9,10],"meta::meta",["Map",3,4,5,6,7,8],"astro-version","5.18.2","content-config-digest","b13dc6e1e58a194d","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"site\":\"https://agentos-sdk.dev\",\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":false,\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[null,null,null,null,null],\"rehypePlugins\":[null,[null,{\"strategy\":\"pre-mermaid\",\"mermaidConfig\":{\"theme\":\"dark\"}}],null,null,null,null],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[],\"actionBodySizeLimit\":1048576},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false}}","docs",["Map",11,12,20,21,28,29,36,37,44,45,52,53,60,61,68,69,76,77,84,85,92,93,100,101,108,109,9,116,122,123,130,131,138,139,146,147,154,155,162,163,170,171,178,179,186,187,194,195,202,203,210,211,218,219,226,227,234,235,242,243,250,251,258,259,266,267,274,275,282,283,291,292,299,300,307,308,315,316,323,324,331,332,339,340,347,348,355,356,362,363,370,371,378,379,386,387,394,395,402,403,410,411,418,419,442,443,458,459,477,478,493,494,510,511,529,530,546,547,564,565,581,582,597,598,613,614,629,630,647,648,664,665,680,681,697,698,714,715,730,731,747,748,764,765,780,781,797,798,813,814,829,830,845,846,864,865,878,879,886,887],"docs/agent-to-agent",{"id":11,"data":13,"body":17,"filePath":18,"digest":19,"deferredRender":16},{"title":14,"description":15,"skill":16},"Agent-to-Agent Communication","Use bindings to let agents communicate with each other.",true,"Agents communicate through [bindings](/docs/bindings). You define a bindings group that lets one agent send work to another, and the agent calls it like any other CLI command.\n\n## Example: code writer + reviewer\n\nThis example gives the writer agent a `review` binding. The writer sends the file's full contents (the VMs share no filesystem), and the binding writes them into a separate reviewer VM and sends a review prompt back through the reviewer.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/agent-to-agent/server.ts\" />\n\u003CCodeSnippet file=\"examples/agent-to-agent/client.ts\" />\n\u003C/CodeGroup>\n\nThe writer agent sees the review binding as a CLI command. Because the VMs share no filesystem, it sends the full file contents, not a path:\n\n```bash\nagentos-review submit --code \"$(cat api.ts)\"\n```\n\nThe binding writes the contents into the reviewer's VM, prompts the reviewer, and returns the review to the writer as JSON.\n\n## Why bindings?\n\nBindings are the natural communication layer between agents because:\n\n- **The agent doesn't need to know about other agents.** It just calls a binding. You can swap the implementation without changing the agent's behavior.\n- **No credentials in the VM.** The binding executes on the server, so it can access other agents directly without exposing connection details.\n- **Composable.** Chain any number of agents by adding more bindings. Each binding is a self-contained bridge to another agent.\n\n## Recommendations\n\n- Each agent has its own isolated VM and filesystem (they share no filesystem). Pass file contents through the binding input, then use `writeFile` in the binding to land them in the other VM.\n- Use [Workflows](/docs/workflows) to make multi-agent pipelines durable across restarts.","src/content/docs/docs/agent-to-agent.mdx","166030497fa3e2b1","docs/approvals",{"id":20,"data":22,"body":25,"filePath":26,"digest":27,"deferredRender":16},{"title":23,"description":24,"skill":16},"Approvals","Approve or deny agent tool use with human-in-the-loop or auto-approve patterns.","When an agent wants to use a tool (write a file, run a command, etc.), it asks for permission. You approve or deny that request, either interactively or with a server-side hook.\n\n- **Human-in-the-loop**: subscribe to `permissionRequest` on the client and respond per-request.\n- **Auto-approve**: use the `onPermissionRequest` server hook to decide without a client round-trip.\n- **Selective approval**: inspect the request and approve some, forward others to the client.\n\n## Permission request flow\n\nWhen an agent wants to use a tool, it emits a `permissionRequest`. Every request is delivered to two places at once, and you respond from whichever fits your app:\n\n- **On the client**: subscribe to the `permissionRequest` event and call `respondPermission(sessionId, permissionId, reply)`.\n- **On the server**: the `onPermissionRequest` hook on the actor runs for every request, with no client round-trip.\n- If neither responds, the request blocks until a reply arrives, then rejects after 120 seconds.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/approvals/client.ts\" />\n\n\u003CCodeSnippet file=\"examples/approvals/human-in-the-loop.ts\" />\n\u003C/CodeGroup>\n\nThe `permissionRequest` event payload:\n\n- **`data.sessionId`**: the session the request belongs to.\n- **`data.request.permissionId`**: the id to pass back to `respondPermission`.\n- **`data.request.description`**: human-readable summary of the requested action.\n- **`data.request.params`**: raw ACP permission details (requested tool, paths, etc.).\n\nReply options for `respondPermission`:\n\n| Reply | Behavior |\n|-------|----------|\n| `\"once\"` | Approve this single request |\n| `\"always\"` | Approve this and all future requests of the same type |\n| `\"reject\"` | Deny the request |\n\n## Patterns\n\n### Auto-approve\n\nThe `onPermissionRequest` hook runs server-side for every permission request before it reaches any client. Useful for fully automated pipelines.\n\n- **Signature**: `onPermissionRequest: async (sessionId, request) => { ... }`.\n- **Inspect**: `request.permissionId`, `request.description`, and `request.params`.\n- **Anything not handled** in the hook is forwarded to the client via the `permissionRequest` event.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/approvals/auto-approve.ts\" />\n\n\u003CCodeSnippet file=\"examples/approvals/auto-approve-client.ts\" />\n\u003C/CodeGroup>\n\n### Selective approval\n\nInspect the permission request to make approval decisions based on the tool or path. Approve some server-side, forward the rest to the client for human review.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/approvals/selective.ts\" />\n\n\u003CCodeSnippet file=\"examples/approvals/selective-client.ts\" />\n\u003C/CodeGroup>\n\n- For interactive applications, subscribe to `permissionRequest` on the client and build an approval UI.\n- If neither the server hook nor the client responds, the agent blocks until a response is given or the action times out.","src/content/docs/docs/approvals.mdx","534a7e2e87e53409","docs/authentication",{"id":28,"data":30,"body":33,"filePath":34,"digest":35,"deferredRender":16},{"title":31,"description":32,"skill":16},"Authentication","Authenticate connections to agentOS actors using Rivet Actor connection params and hooks.","agentOS uses the same authentication system as [Rivet Actors](/docs/actors/authentication): clients send credentials as connection params, and you validate them server-side.\n\n- Clients pass credentials in `params` when they connect.\n- Validate them on the server in `onBeforeConnect` (throw to reject the connection), or extract user data into connection state with `createConnState` (read it in actions via `c.conn.state`).\n- You can declare the credential shape with `agentOS\u003CConnParams>(...)` to document what you accept, but the client's `params` is `unknown` and is not checked against it. The real check is your hook, not the types.\n- The current `@rivet-dev/agentos` runtime is an interim stub, so wiring these hooks end to end depends on the native runtime landing.\n\n## Example\n\nThe server declares the credential shape and validates it in `onBeforeConnect` (throw to reject); the client passes credentials as `params`.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/authentication/server.ts\" />\n\n\u003CCodeSnippet file=\"examples/authentication/client.ts\" />\n\u003C/CodeGroup>\n\nSee [Actor Authentication](/docs/actors/authentication) for JWT validation, role-based access control, external auth providers, and token caching.","src/content/docs/docs/authentication.mdx","49d6839b45690446","docs/architecture",{"id":36,"data":38,"body":41,"filePath":42,"digest":43,"deferredRender":16},{"title":39,"description":40,"skill":16},"Overview","A high-level tour of how agentOS works: the client / server / VM picture, the anatomy of a Linux VM (kernel + executor), agents and sessions, and the Rivet Actor orchestration underneath.","agentOS runs AI agents and untrusted code safely inside fully virtualized Linux VMs. Nothing the guest does touches your host directly: there is no real host filesystem, no real host network socket, and no real host process. Every guest operation is serviced by a kernel that agentOS owns.\n\nThis page is a high-level tour. It walks through the overall shape, the parts that make up a VM, how agent sessions work, and the orchestration layer underneath. Each section links out to a detailed page when you want to go deeper.\n\n## The big picture\n\nA running agentOS system has three roles: your **app** (the client), your **server** (which runs the sidecar that hosts the VMs), and the **VM** where guest code actually runs. Your app never runs guest code itself, it asks the server to.\n\n\u003Csvg viewBox=\"0 0 400 210\" role=\"img\" aria-label=\"A client (JavaScript, browser, or another backend) connects to an agentOS server. The server runs a sidecar that hosts many isolated VMs, each marked with the agentOS 'OS' logo; the sidecar brokers all guest syscalls and isolates each agent.\" style=\"width:100%;height:auto;max-width:420px;display:block;margin:2.5rem auto 0.5rem;\">\n \u003Cdefs>\n \u003Cmarker id=\"bp-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003Csymbol id=\"bp-os\" viewBox=\"0 0 100 100\">\n \u003Crect x=\"8\" y=\"8\" width=\"84\" height=\"84\" rx=\"26\" fill=\"none\" stroke=\"#1b1916\" stroke-width=\"8\" />\n \u003Ctext x=\"50\" y=\"50\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"var(--sl-font)\" font-weight=\"700\" font-size=\"38\" fill=\"#1b1916\">OS\u003C/text>\n \u003C/symbol>\n \u003C/defs>\n \u003Crect x=\"12\" y=\"67\" width=\"140\" height=\"60\" rx=\"12\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"82\" y=\"92\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"15\" font-weight=\"600\" fill=\"#1b1916\">Client\u003C/text>\n \u003Ctext x=\"82\" y=\"112\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#56524a\">JS · Browser · Backend\u003C/text>\n \u003Cline x1=\"154\" y1=\"97\" x2=\"205\" y2=\"97\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#bp-arrow)\" />\n \u003Crect x=\"210\" y=\"40\" width=\"164\" height=\"114\" rx=\"14\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"224\" y=\"62\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">Server\u003C/text>\n \u003Cg fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.2\">\n \u003Crect x=\"224\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"260\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"296\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"332\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"224\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"260\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"296\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"332\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003C/g>\n \u003Cg>\n \u003Cuse href=\"#bp-os\" x=\"229\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"265\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"301\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"337\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"229\" y=\"117\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"265\" y=\"117\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"301\" y=\"117\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"337\" y=\"117\" width=\"18\" height=\"18\" />\n \u003C/g>\n \u003Cg>\n \u003Crect x=\"150\" y=\"170\" width=\"15\" height=\"15\" rx=\"4\" fill=\"none\" stroke=\"#56524a\" stroke-width=\"1.4\" />\n \u003Ctext x=\"157.5\" y=\"178\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"var(--sl-font)\" font-weight=\"700\" font-size=\"7\" fill=\"#56524a\">OS\u003C/text>\n \u003Ctext x=\"174\" y=\"178\" dominant-baseline=\"central\" font-family=\"var(--sl-font)\" font-size=\"12\" fill=\"#56524a\">= an isolated VM\u003C/text>\n \u003C/g>\n\u003C/svg>\n\nThe client speaks to the agentOS server over the wire. The server runs the **sidecar**, the trusted core that hosts every VM: it owns each VM's kernel and brokers every guest syscall the agent makes (filesystem, processes, network, permissions) before carrying it out. Each VM is a fully isolated world, so agents are isolated from one another and from your host.\n\n### Your app (the client)\n\n- **Trusted caller.** Your app drives agentOS. It creates VMs, opens sessions, sends prompts, and reads results back.\n- **Never runs guest code.** The agent and any code it generates run in the VM, not in your app's process.\n- **Available everywhere.** There is a TypeScript client and a Rust client, and the same VM is reachable from a Node script, a browser/React app, or a separate backend.\n- **Owns the configuration.** Everything you send (VM setup, permission policy, resource limits, mounts) is trusted input. See the [Security Model](/docs/security-model) for why your configuration is not an attack surface.\n\n### Your server (the sidecar)\n\n- **The trusted core.** The sidecar is the part of the system that owns everything: the kernel, the virtual filesystem, the process and socket tables, pipes, PTYs, the permission policy, and DNS.\n- **The enforcement point.** Every request the VM makes is serviced here. The sidecar decides what is allowed before carrying it out.\n- **Hosts every VM.** A single sidecar manages many VMs side by side, each with its own kernel, filesystem, and process table, so every agent runs in its own isolated world. A crash or runaway in one VM never affects another.\n\n### The VM\n\n- **A fully virtualized Linux environment.** Each VM has its own filesystem, process table, and network policy. Two VMs share nothing.\n- **The unit of isolation.** Put one tenant or one task per VM to control the blast radius. A crash or runaway in one VM never affects another.\n- **Where guest code lives.** The agent, the shell, npm packages, and any generated code all run inside the VM, behind the kernel's boundary.\n\n## Anatomy of a Linux VM\n\nInside every VM there are two halves. The **kernel** is the trusted core that owns all the resources and rules. The **executor** is where untrusted guest code actually runs. Guest code can only *ask* the kernel for things, it never holds a real capability of its own.\n\n\u003Csvg viewBox=\"0 0 700 360\" role=\"img\" aria-label=\"A VM split into a kernel and an executor. The kernel owns the virtual filesystem, process table, socket table, pipes, PTYs, DNS, and permission policy. The executor runs guest JavaScript, WASM, and native binaries, and reaches the kernel through syscalls.\" style=\"width:100%;height:auto;max-width:680px;display:block;margin:1.5rem auto 0.5rem;\">\n \u003Cdefs>\n \u003Cmarker id=\"vm-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"12\" y=\"12\" width=\"676\" height=\"336\" rx=\"14\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"32\" y=\"40\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">The VM\u003C/text>\n\n \u003Crect x=\"32\" y=\"56\" width=\"636\" height=\"150\" rx=\"10\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.3\" />\n \u003Ctext x=\"52\" y=\"82\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">Kernel\u003C/text>\n \u003Ctext x=\"52\" y=\"100\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#56524a\">trusted core, every operation goes through here\u003C/text>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"11\" fill=\"#1b1916\">\n \u003Crect x=\"52\" y=\"116\" width=\"118\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"111\" y=\"135\" text-anchor=\"middle\">virtual filesystem\u003C/text>\n \u003Crect x=\"182\" y=\"116\" width=\"118\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"241\" y=\"135\" text-anchor=\"middle\">process table\u003C/text>\n \u003Crect x=\"312\" y=\"116\" width=\"118\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"371\" y=\"135\" text-anchor=\"middle\">socket table\u003C/text>\n \u003Crect x=\"442\" y=\"116\" width=\"92\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"488\" y=\"135\" text-anchor=\"middle\">pipes / PTYs\u003C/text>\n \u003Crect x=\"546\" y=\"116\" width=\"100\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"596\" y=\"135\" text-anchor=\"middle\">DNS\u003C/text>\n \u003Crect x=\"52\" y=\"156\" width=\"594\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"349\" y=\"175\" text-anchor=\"middle\">permission policy · network allowlist · resource limits\u003C/text>\n \u003C/g>\n\n \u003Cline x1=\"349\" y1=\"206\" x2=\"349\" y2=\"244\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#vm-arrow)\" />\n \u003Cline x1=\"319\" y1=\"244\" x2=\"319\" y2=\"206\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#vm-arrow)\" />\n \u003Ctext x=\"430\" y=\"228\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#56524a\">syscalls / replies\u003C/text>\n\n \u003Crect x=\"32\" y=\"248\" width=\"636\" height=\"84\" rx=\"10\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.3\" />\n \u003Ctext x=\"52\" y=\"274\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">Executor\u003C/text>\n \u003Ctext x=\"52\" y=\"292\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#56524a\">untrusted, runs guest code, holds no capabilities\u003C/text>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"11\" fill=\"#1b1916\">\n \u003Crect x=\"382\" y=\"262\" width=\"170\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"467\" y=\"281\" text-anchor=\"middle\">guest JavaScript (native V8)\u003C/text>\n \u003Crect x=\"562\" y=\"262\" width=\"84\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"604\" y=\"281\" text-anchor=\"middle\">WASM\u003C/text>\n \u003Crect x=\"382\" y=\"298\" width=\"264\" height=\"22\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"514\" y=\"313\" text-anchor=\"middle\" font-size=\"10.5\">shell · coreutils · npm packages · native binaries\u003C/text>\n \u003C/g>\n\u003C/svg>\n\n### Kernel: the trusted core\n\n\u003Csvg viewBox=\"0 0 480 150\" role=\"img\" aria-label=\"Guest requests funnel into a single kernel chokepoint, which fans out to its owned subsystems: filesystem, processes, network, and policy.\" style=\"width:100%;height:auto;max-width:440px;display:block;margin:2.5rem auto;\">\n \u003Cdefs>\n \u003Cmarker id=\"kn-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"14\" y=\"58\" width=\"92\" height=\"34\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.3\" />\n \u003Ctext x=\"60\" y=\"79\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"11\" fill=\"#56524a\">guest request\u003C/text>\n \u003Cline x1=\"106\" y1=\"75\" x2=\"150\" y2=\"75\" stroke=\"#1b1916\" stroke-width=\"1.4\" marker-end=\"url(#kn-arrow)\" />\n \u003Crect x=\"154\" y=\"50\" width=\"92\" height=\"50\" rx=\"10\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.4\" />\n \u003Ctext x=\"200\" y=\"79\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"12\" font-weight=\"600\" fill=\"#1b1916\">Kernel\u003C/text>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#1b1916\">\n \u003Cline x1=\"246\" y1=\"60\" x2=\"286\" y2=\"22\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#kn-arrow)\" />\n \u003Cline x1=\"246\" y1=\"70\" x2=\"286\" y2=\"58\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#kn-arrow)\" />\n \u003Cline x1=\"246\" y1=\"80\" x2=\"286\" y2=\"92\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#kn-arrow)\" />\n \u003Cline x1=\"246\" y1=\"90\" x2=\"286\" y2=\"128\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#kn-arrow)\" />\n \u003Crect x=\"290\" y=\"8\" width=\"176\" height=\"26\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"378\" y=\"25\" text-anchor=\"middle\">filesystem\u003C/text>\n \u003Crect x=\"290\" y=\"46\" width=\"176\" height=\"26\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"378\" y=\"63\" text-anchor=\"middle\">processes\u003C/text>\n \u003Crect x=\"290\" y=\"80\" width=\"176\" height=\"26\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"378\" y=\"97\" text-anchor=\"middle\">network & DNS\u003C/text>\n \u003Crect x=\"290\" y=\"116\" width=\"176\" height=\"26\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"378\" y=\"133\" text-anchor=\"middle\">policy & limits\u003C/text>\n \u003C/g>\n\u003C/svg>\n\nThe kernel is the single chokepoint. Each kind of guest operation is serviced by a kernel-owned subsystem, never by a real host capability.\n\n- **Virtual filesystem.** A per-VM filesystem. Guest reads and writes hit the VFS, not your host disk.\n- **Process table.** A virtual process table. Child processes are kernel-managed and visible only inside their VM. No real host process is ever spawned for guest work.\n- **Socket table and DNS.** A virtual network stack. Outbound traffic is gated by the network allowlist.\n- **Pipes and PTYs.** Kernel-owned IPC and terminal devices, so shells and pipelines behave like real Linux.\n- **Policy and limits.** The kernel checks the applied permission policy, network allowlist, and resource limits on every request.\n\n### Executor: where guest code runs\n\n\u003Csvg viewBox=\"0 0 480 130\" role=\"img\" aria-label=\"The untrusted executor runs guest JavaScript, WASM, and native binaries. It holds no capabilities and reaches the kernel through syscalls.\" style=\"width:100%;height:auto;max-width:440px;display:block;margin:2.5rem auto;\">\n \u003Cdefs>\n \u003Cmarker id=\"ex-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"14\" y=\"14\" width=\"300\" height=\"102\" rx=\"10\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.4\" />\n \u003Ctext x=\"30\" y=\"36\" font-family=\"var(--sl-font)\" font-size=\"12\" font-weight=\"600\" fill=\"#1b1916\">Executor\u003C/text>\n \u003Ctext x=\"30\" y=\"52\" font-family=\"var(--sl-font)\" font-size=\"9.5\" fill=\"#56524a\">untrusted · no capabilities\u003C/text>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#1b1916\">\n \u003Crect x=\"30\" y=\"62\" width=\"80\" height=\"44\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"70\" y=\"88\" text-anchor=\"middle\">JS (V8)\u003C/text>\n \u003Crect x=\"120\" y=\"62\" width=\"80\" height=\"44\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"160\" y=\"88\" text-anchor=\"middle\">WASM\u003C/text>\n \u003Crect x=\"210\" y=\"62\" width=\"92\" height=\"44\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"256\" y=\"82\" text-anchor=\"middle\">native\u003C/text>\u003Ctext x=\"256\" y=\"96\" text-anchor=\"middle\">binaries\u003C/text>\n \u003C/g>\n \u003Cline x1=\"314\" y1=\"55\" x2=\"358\" y2=\"55\" stroke=\"#1b1916\" stroke-width=\"1.4\" marker-end=\"url(#ex-arrow)\" />\n \u003Ctext x=\"336\" y=\"48\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"9\" fill=\"#56524a\">syscall\u003C/text>\n \u003Cline x1=\"358\" y1=\"75\" x2=\"314\" y2=\"75\" stroke=\"#1b1916\" stroke-width=\"1.4\" marker-end=\"url(#ex-arrow)\" />\n \u003Ctext x=\"336\" y=\"92\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"9\" fill=\"#56524a\">reply\u003C/text>\n \u003Crect x=\"362\" y=\"38\" width=\"104\" height=\"54\" rx=\"10\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.4\" />\n \u003Ctext x=\"414\" y=\"70\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"12\" font-weight=\"600\" fill=\"#1b1916\">Kernel\u003C/text>\n\u003C/svg>\n\nThe executor is the untrusted half of the VM. It runs the guest code and reaches the kernel for everything else.\n\n- **JavaScript Acceleration.** Guest JavaScript runs on a native V8 runtime (the same engine in Chrome and Node.js, with the full JIT compiler) inside an isolate. This is what we call **JavaScript Acceleration**: the guest's JavaScript executes at native speed, not through an interpreter or a translation shim. It is genuinely fast, and it presents normal Node.js semantics. See [JavaScript Runtime](/docs/nodejs-runtime).\n- **WASM alongside it.** The shell (`sh`) and the coreutils behind process execution ship as WebAssembly modules, and you can run your own WASM too. See [POSIX Syscalls](/docs/architecture/posix-syscalls) and the [Compiler Toolchain](/docs/architecture/compiler-toolchain).\n- **Native binaries.** Tools mounted into the VM run inside the same boundary as everything else.\n- **No host fallthrough.** The executor holds no capability of its own. For every file read, process spawn, or socket open, it issues a syscall and blocks for the kernel's reply.\n\n### Processes & shell\n\n\u003Csvg viewBox=\"0 0 480 120\" role=\"img\" aria-label=\"exec, run, and spawn create entries in the kernel-owned virtual process table, with stdio bridged through pipes and PTYs.\" style=\"width:100%;height:auto;max-width:440px;display:block;margin:2.5rem auto;\">\n \u003Cdefs>\n \u003Cmarker id=\"pr-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"11\" fill=\"#1b1916\">\n \u003Crect x=\"14\" y=\"14\" width=\"92\" height=\"26\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"60\" y=\"31\" text-anchor=\"middle\">exec() / run()\u003C/text>\n \u003Crect x=\"14\" y=\"78\" width=\"92\" height=\"26\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"60\" y=\"95\" text-anchor=\"middle\">spawn / shell\u003C/text>\n \u003C/g>\n \u003Cline x1=\"106\" y1=\"27\" x2=\"172\" y2=\"52\" stroke=\"#1b1916\" stroke-width=\"1.3\" marker-end=\"url(#pr-arrow)\" />\n \u003Cline x1=\"106\" y1=\"91\" x2=\"172\" y2=\"66\" stroke=\"#1b1916\" stroke-width=\"1.3\" marker-end=\"url(#pr-arrow)\" />\n \u003Crect x=\"176\" y=\"36\" width=\"138\" height=\"46\" rx=\"10\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.4\" />\n \u003Ctext x=\"245\" y=\"56\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"11.5\" font-weight=\"600\" fill=\"#1b1916\">process table\u003C/text>\n \u003Ctext x=\"245\" y=\"71\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"9\" fill=\"#56524a\">virtual · per-VM\u003C/text>\n \u003Cline x1=\"314\" y1=\"59\" x2=\"360\" y2=\"59\" stroke=\"#1b1916\" stroke-width=\"1.3\" marker-end=\"url(#pr-arrow)\" />\n \u003Crect x=\"364\" y=\"40\" width=\"102\" height=\"40\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.2\" />\n \u003Ctext x=\"415\" y=\"64\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#1b1916\">pipes & PTYs\u003C/text>\n\u003C/svg>\n\n- **A real process model.** `exec()` and `run()` start fresh guest processes; you can also `spawn` long-running ones and open interactive shells.\n- **Kernel-managed.** Every process lives in the virtual process table, with stdio bridged through kernel-owned pipes and PTYs.\n- **Fresh each run.** Each `exec()` / `run()` starts a brand new guest process, so in-memory state never leaks from one run into the next.\n- See [Processes](/docs/architecture/processes) for the internals.\n\n### Virtual filesystem\n\n\u003Csvg viewBox=\"0 0 480 150\" role=\"img\" aria-label=\"The virtual filesystem layers a writable overlay over a snapshot root, plus mount points that graft host directories, S3, or cloud stores onto guest paths.\" style=\"width:100%;height:auto;max-width:440px;display:block;margin:2.5rem auto;\">\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"11\" fill=\"#1b1916\">\n \u003Crect x=\"60\" y=\"14\" width=\"360\" height=\"30\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.2\" />\u003Ctext x=\"240\" y=\"33\" text-anchor=\"middle\">overlay (guest writes)\u003C/text>\n \u003Crect x=\"60\" y=\"52\" width=\"360\" height=\"30\" rx=\"8\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.2\" />\u003Ctext x=\"240\" y=\"71\" text-anchor=\"middle\">root layer (snapshot)\u003C/text>\n \u003C/g>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"9.5\" fill=\"#1b1916\">\n \u003Crect x=\"60\" y=\"104\" width=\"108\" height=\"32\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"114\" y=\"124\" text-anchor=\"middle\">host dir mount\u003C/text>\n \u003Crect x=\"186\" y=\"104\" width=\"108\" height=\"32\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"240\" y=\"124\" text-anchor=\"middle\">S3 mount\u003C/text>\n \u003Crect x=\"312\" y=\"104\" width=\"108\" height=\"32\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"366\" y=\"124\" text-anchor=\"middle\">cloud store\u003C/text>\n \u003C/g>\n \u003Ctext x=\"240\" y=\"98\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"9\" fill=\"#56524a\">mount points grafted onto guest paths\u003C/text>\n\u003C/svg>\n\n- **Layered engines.** The VFS is a tree of engines: a root layer bootstrapped from a snapshot, an overlay for writes, and mount points that graft other backends onto guest paths.\n- **Host-backed mounts.** A guest path can be backed by a host directory, S3, or a cloud store. The kernel confines all guest I/O to the mount root, even against symlink and `..` tricks.\n- **Persisted.** The `/home/agentos` filesystem survives sleep/wake.\n- See [Filesystem](/docs/architecture/filesystem) for the internals.\n\n### Networking\n\n\u003Csvg viewBox=\"0 0 480 150\" role=\"img\" aria-label=\"Guest fetch, node:http, node:net, and WASM sockets all converge on one kernel socket table, which gates outbound traffic through the network allowlist.\" style=\"width:100%;height:auto;max-width:440px;display:block;margin:2.5rem auto;\">\n \u003Cdefs>\n \u003Cmarker id=\"nw-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#1b1916\">\n \u003Crect x=\"14\" y=\"10\" width=\"92\" height=\"24\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"60\" y=\"26\" text-anchor=\"middle\">fetch()\u003C/text>\n \u003Crect x=\"14\" y=\"42\" width=\"92\" height=\"24\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"60\" y=\"58\" text-anchor=\"middle\">node:http\u003C/text>\n \u003Crect x=\"14\" y=\"74\" width=\"92\" height=\"24\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"60\" y=\"90\" text-anchor=\"middle\">node:net\u003C/text>\n \u003Crect x=\"14\" y=\"106\" width=\"92\" height=\"24\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"60\" y=\"122\" text-anchor=\"middle\">WASM sockets\u003C/text>\n \u003C/g>\n \u003Cline x1=\"106\" y1=\"22\" x2=\"186\" y2=\"62\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#nw-arrow)\" />\n \u003Cline x1=\"106\" y1=\"54\" x2=\"186\" y2=\"66\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#nw-arrow)\" />\n \u003Cline x1=\"106\" y1=\"86\" x2=\"186\" y2=\"74\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#nw-arrow)\" />\n \u003Cline x1=\"106\" y1=\"118\" x2=\"186\" y2=\"78\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#nw-arrow)\" />\n \u003Crect x=\"190\" y=\"48\" width=\"116\" height=\"44\" rx=\"10\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.4\" />\n \u003Ctext x=\"248\" y=\"68\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"11\" font-weight=\"600\" fill=\"#1b1916\">socket table\u003C/text>\n \u003Ctext x=\"248\" y=\"83\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"9\" fill=\"#56524a\">kernel-owned\u003C/text>\n \u003Cline x1=\"306\" y1=\"70\" x2=\"350\" y2=\"70\" stroke=\"#1b1916\" stroke-width=\"1.4\" marker-end=\"url(#nw-arrow)\" />\n \u003Crect x=\"354\" y=\"50\" width=\"112\" height=\"40\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.2\" />\n \u003Ctext x=\"410\" y=\"74\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#1b1916\">egress allowlist\u003C/text>\n\u003C/svg>\n\n- **One authoritative transport.** Guest `fetch()`, `node:http`, `node:net`, and WASM sockets all target the same kernel socket table. No part of guest networking opens a real host socket on its own.\n- **Egress policy.** Outbound traffic is gated by the network allowlist; loopback traffic stays confined to the VM.\n- **Preview URLs.** Servers a guest starts can be exposed through signed preview URLs.\n- See [Networking](/docs/architecture/networking) for the internals.\n\n\u003CNote>The security boundary that matters is between the trusted sidecar and the untrusted executor. Everything the guest tries to do crosses into the kernel, where the policy is checked before the operation runs. See the [Security Model](/docs/security-model) for the full threat model.\u003C/Note>\n\n## Agents & sessions\n\nAn agent (such as [Pi](https://github.com/mariozechner/pi-coding-agent)) is just another guest process running inside a VM, behind the same boundary as any other code. A **session** keeps that agent alive across many prompts and streams its output back to your app as events.\n\n\u003Csvg viewBox=\"0 0 700 210\" role=\"img\" aria-label=\"A client sends a prompt to an agent running inside a VM. The agent streams events back to the client and persists a transcript.\" style=\"width:100%;height:auto;max-width:680px;display:block;margin:1.5rem auto 0.5rem;\">\n \u003Cdefs>\n \u003Cmarker id=\"se-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"12\" y=\"66\" width=\"150\" height=\"78\" rx=\"12\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"87\" y=\"98\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"14\" font-weight=\"600\" fill=\"#1b1916\">Client\u003C/text>\n \u003Ctext x=\"87\" y=\"118\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#56524a\">your app\u003C/text>\n\n \u003Cline x1=\"162\" y1=\"92\" x2=\"266\" y2=\"92\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#se-arrow)\" />\n \u003Ctext x=\"214\" y=\"84\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#56524a\">prompt\u003C/text>\n \u003Cline x1=\"266\" y1=\"120\" x2=\"162\" y2=\"120\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#se-arrow)\" />\n \u003Ctext x=\"214\" y=\"136\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#56524a\">events\u003C/text>\n\n \u003Crect x=\"270\" y=\"30\" width=\"280\" height=\"150\" rx=\"12\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"290\" y=\"56\" font-family=\"var(--sl-font)\" font-size=\"12\" font-weight=\"600\" fill=\"#1b1916\">The VM\u003C/text>\n \u003Crect x=\"300\" y=\"72\" width=\"220\" height=\"56\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.2\" />\n \u003Ctext x=\"410\" y=\"98\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">Agent session\u003C/text>\n \u003Ctext x=\"410\" y=\"116\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#56524a\">long-lived agent process\u003C/text>\n\n \u003Cline x1=\"550\" y1=\"105\" x2=\"630\" y2=\"105\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#se-arrow)\" />\n \u003Crect x=\"560\" y=\"72\" width=\"118\" height=\"66\" rx=\"10\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.3\" />\n \u003Ctext x=\"619\" y=\"100\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"12\" font-weight=\"600\" fill=\"#1b1916\">Transcript\u003C/text>\n \u003Ctext x=\"619\" y=\"118\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#56524a\">persisted, replayable\u003C/text>\n\u003C/svg>\n\n### Sessions & transcripts\n\n- **Long-lived.** Where a bare `exec()` runs once and exits, a session keeps an agent alive across many prompts.\n- **Streamed.** The agent's output flows back to your app in real time as `sessionEvent`s.\n- **Replayable.** Each session persists a transcript (with sequence numbers) that survives sleep/wake, so you can replay the conversation later.\n- **Context injected.** agentOS adds a system prompt describing the VM environment and available commands and bindings, layered on top of the agent's own instructions. See [System Prompt](/docs/system-prompt).\n- See [Agent Sessions](/docs/architecture/agent-sessions) for the internals.\n\n### Permissions & approvals\n\n- **Two layers, different jobs.** The lower-level [permission policy](/docs/permissions) is enforced by the kernel on every guest syscall (nothing is allowed until you opt in). On top of that, [approvals](/docs/approvals) are about an agent asking before it uses a tool.\n- **Human-in-the-loop or automatic.** Subscribe to `permissionRequest` and respond per request, or use a server-side hook to decide without a client round-trip.\n- **Blocks until answered.** If neither your hook nor your client responds, the agent waits rather than proceeding.\n\n## Orchestration (Rivet Actors)\n\nThe `agentOS()` actor (from `@rivet-dev/agentos`) wraps the raw VM in a [Rivet Actor](/docs/core), which adds durable state, scheduling, and orchestration. This is what gives you persistence, cron, and workflows out of the box.\n\n\u003Csvg viewBox=\"0 0 700 200\" role=\"img\" aria-label=\"A Rivet Actor wraps an agentOS VM and adds durable state, cron scheduling, workflows, and sleep/wake persistence.\" style=\"width:100%;height:auto;max-width:680px;display:block;margin:1.5rem auto 0.5rem;\">\n \u003Crect x=\"40\" y=\"20\" width=\"620\" height=\"160\" rx=\"14\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"64\" y=\"46\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">Rivet Actor\u003C/text>\n \u003Ctext x=\"64\" y=\"64\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#56524a\">durable, addressable server object\u003C/text>\n\n \u003Crect x=\"64\" y=\"80\" width=\"180\" height=\"80\" rx=\"10\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.3\" />\n \u003Ctext x=\"154\" y=\"116\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">agentOS VM\u003C/text>\n \u003Ctext x=\"154\" y=\"136\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#56524a\">the virtual Linux VM\u003C/text>\n\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"11.5\" fill=\"#1b1916\">\n \u003Crect x=\"272\" y=\"80\" width=\"120\" height=\"34\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.1\" />\u003Ctext x=\"332\" y=\"102\" text-anchor=\"middle\">Cron\u003C/text>\n \u003Crect x=\"412\" y=\"80\" width=\"120\" height=\"34\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.1\" />\u003Ctext x=\"472\" y=\"102\" text-anchor=\"middle\">Workflows\u003C/text>\n \u003Crect x=\"272\" y=\"126\" width=\"260\" height=\"34\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.1\" />\u003Ctext x=\"402\" y=\"148\" text-anchor=\"middle\">Persistence · sleep / wake\u003C/text>\n \u003Crect x=\"552\" y=\"80\" width=\"92\" height=\"80\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.1\" />\u003Ctext x=\"598\" y=\"116\" text-anchor=\"middle\">Durable\u003C/text>\u003Ctext x=\"598\" y=\"132\" text-anchor=\"middle\">state\u003C/text>\n \u003C/g>\n\u003C/svg>\n\n### What are actors?\n\n- **Durable server objects.** A Rivet Actor is a long-lived, addressable object with its own state. You reach a specific VM by name (`vm.getOrCreate(\"my-agent\")`).\n- **Stateful by default.** Unlike the bare core package, the actor keeps its filesystem and sessions persistent and handles distributed state for you.\n- **The portable runtime.** Actors give you a consistent way to run `agentOS()` on any infrastructure, with persistence, networking, and orchestration built in.\n\n### Cron\n\n- **Recurring work.** Schedule a shell command or an agent session on a cron expression.\n- **Overlap control.** Choose what happens when a run is still going when the next is due (`allow`, `skip`, or `queue`).\n- **Observable.** Stream `cronEvent`s to watch executions. See [Cron Jobs](/docs/cron).\n\n### Workflows\n\n- **Durable multi-step tasks.** A workflow is the actor's `run` handler wrapped in `workflow()`, where each `ctx.step()` is recorded, retried, and resumed independently.\n- **Crash-proof.** If the process dies mid-run, replay skips completed steps and continues where it left off.\n- **Composable.** The output of one step feeds the next: clone a repo, let an agent fix a bug, run the tests. See [Workflow Automation](/docs/workflows).\n\n### Persistence & sleep/wake\n\n- **Sleeps when idle.** After a grace period (15 minutes by default) with no activity, the VM sleeps to free resources.\n- **Wakes on demand.** It wakes automatically when a client connects or a cron job fires.\n- **What survives.** The `/home/agentos` filesystem, session records, transcripts, preview tokens, and cron definitions all persist. In-memory kernel state (running processes, open shells) does not. See [Persistence & Sleep](/docs/persistence).\n\n## Going deeper\n\nThis page is the map. Each subsystem has its own detailed page in the Advanced architecture section:\n\n- **[Agent Sessions](/docs/architecture/agent-sessions)**: how a session is bound to a VM, and how prompts and events flow end to end.\n- **[Processes](/docs/architecture/processes)**: the virtual process table, `exec()` / `run()`, child processes, and PTYs.\n- **[Filesystem](/docs/architecture/filesystem)**: the per-VM virtual filesystem, overlays, and host-backed mounts.\n- **[Networking](/docs/architecture/networking)**: the virtual socket table, DNS, the allowlist, and guest `fetch()`.\n- **[POSIX Syscalls](/docs/architecture/posix-syscalls)**: how WebAssembly guests behave like normal POSIX programs on top of the kernel.\n- **[Compiler Toolchain](/docs/architecture/compiler-toolchain)**: how the shell and coreutils are compiled to WebAssembly and mounted into the VM.\n- **[System Prompt](/docs/system-prompt)**: the context agentOS injects into every agent session.\n- **[Persistence & Sleep](/docs/persistence)**: what survives sleep/wake, and how VMs sleep and wake.\n\nFor the trust model and what counts as a sandbox escape, see the [Security Model](/docs/security-model).","src/content/docs/docs/architecture.mdx","e0003621fdf9ed78","docs/benchmarks",{"id":44,"data":46,"body":49,"filePath":50,"digest":51,"deferredRender":16},{"title":47,"description":48,"skill":16},"Benchmarks","Performance benchmarks comparing agentOS to traditional sandbox providers.","These are the benchmark figures shown on the agentOS marketing page. All numbers are computed from the same data source used by the marketing page. For independent sandbox comparison data, see the [ComputeSDK benchmarks](https://www.computesdk.com/benchmarks/).\n\n## Cold start\n\nTime from requesting an execution to first code running. Measured using the sleep workload (a minimal VM running an idle Node.js process). Sandbox baseline: **E2B**, the fastest mainstream sandbox provider as of March 30, 2026. See [ComputeSDK benchmarks](https://www.computesdk.com/benchmarks/) for independent sandbox comparison data.\n\n| Metric | agentOS | Fastest sandbox (E2B) |\n|---|--:|--:|\n| Cold start p50 | 4.8 ms | 440 ms |\n| Cold start p95 | 5.6 ms | 950 ms |\n| Cold start p99 | 6.1 ms | 3,150 ms |\n\n## Memory per instance\n\nMeasured via staircase benchmarking:\n\n1. **Warmup.** A throwaway VM is created, started, and destroyed before measurement begins. This pays one-time costs (module cache, JIT compilation) that are amortized away in any real deployment where the host process is long-lived.\n2. **Baseline.** GC is forced twice (`--expose-gc`), then RSS is sampled across the entire process tree by reading `/proc/[pid]/statm` for the host process and all descendants. This captures child processes (e.g. V8 isolates running as separate processes) that `process.memoryUsage().rss` would miss.\n3. **Staircase.** VMs are added one at a time. After each VM starts and settles, GC is forced and RSS is sampled again. The delta from the previous sample is the incremental cost of that VM.\n4. **Average.** The per-VM cost is the mean of all step deltas.\n5. **Teardown.** All VMs are disposed and the reclaimed RSS is recorded.\n\nRSS is a process-wide metric that includes thread stacks and OS-mapped pages beyond the VM itself, so the reported figure is an upper bound on the true per-VM cost.\n\nSandbox baseline: **Daytona**, the cheapest mainstream sandbox provider as of March 30, 2026. Default sandbox: 1 vCPU + 1 GiB RAM.\n\n### Full coding agent\n\nPi coding agent session with MCP servers and mounted file systems.\n\n| Metric | agentOS | Cheapest sandbox (Daytona) |\n|---|--:|--:|\n| Memory per instance | ~131 MB | ~1024 MB |\n\n### Simple shell command\n\nMinimal shell workload running simple commands.\n\n| Metric | agentOS | Cheapest sandbox (Daytona) |\n|---|--:|--:|\n| Memory per instance | ~22 MB | ~1024 MB |\n\n## Cost per execution-second\n\nAssumes one agent per sandbox (needed for isolation) and 70% host utilization for self-hosted hardware (the industry-standard HPA scaling threshold). Cost formula: `server cost per second / concurrent executions per server`, where concurrent executions = `floor(server RAM / agent memory) × 0.7`.\n\nSandbox baseline: **Daytona** at $0.0504/vCPU-h + $0.0162/GiB-h with a 1 vCPU + 1 GiB minimum. Source: [daytona.io/pricing](https://www.daytona.io/pricing).\n\n### Full coding agent\n\n| Host tier | agentOS | Cheapest sandbox | Difference |\n|---|--:|--:|--:|\n| AWS ARM | $0.00000058/s | $0.000018/s | 32x cheaper |\n| AWS x86 | $0.00000072/s | $0.000018/s | 26x cheaper |\n| Hetzner ARM | $0.000000066/s | $0.000018/s | 281x cheaper |\n| Hetzner x86 | $0.00000011/s | $0.000018/s | 171x cheaper |\n\n### Simple shell command\n\n| Host tier | agentOS | Cheapest sandbox | Difference |\n|---|--:|--:|--:|\n| AWS ARM | $0.000000073/s | $0.000018/s | 254x cheaper |\n| AWS x86 | $0.000000090/s | $0.000018/s | 205x cheaper |\n| Hetzner ARM | $0.000000011/s | $0.000018/s | 1738x cheaper |\n| Hetzner x86 | $0.000000017/s | $0.000018/s | 1061x cheaper |\n\n## Test environment\n\n| Component | Details |\n|---|---|\n| CPU | 12th Gen Intel i7-12700KF, 12 cores / 20 threads @ 3.7 GHz, 25 MB cache |\n| RAM | 2× 32 GB DDR4 @ 2400 MT/s |\n| Node.js | v24.13.0 |\n| OS | Linux 6.1.0 (Debian), x86_64 |\n\n## Sandbox baselines\n\n| Comparison | Provider | Why this provider |\n|---|---|---|\n| Cold start | E2B | Fastest mainstream sandbox provider on [ComputeSDK](https://www.computesdk.com/benchmarks/) as of March 30, 2026 |\n| Memory and cost | Daytona | Cheapest mainstream sandbox provider as of March 30, 2026 ($0.0504/vCPU-h + $0.0162/GiB-h) |\n\nSelf-hosted hardware tiers: AWS t4g.micro (ARM, $0.0084/h, 1 GiB), AWS t3.micro (x86, $0.0104/h, 1 GiB), Hetzner CAX11 (ARM, €3.29/mo, 4 GiB), Hetzner CX22 (x86, €5.39/mo, 4 GiB). All on-demand pricing.\n\n## Reproducing\n\nagentOS benchmarks live in the [agent-os repository](https://github.com/rivet-dev/agentos) under `scripts/benchmarks/`.\n\n### Prerequisites\n\n- Node.js (see `.nvmrc`) and `pnpm`, with dependencies installed: `pnpm install`\n- A Rust toolchain (`cargo`) — the benchmarks build and run the native release sidecar\n- A reasonably **idle machine**: cold-start latency tails are sensitive to background CPU and GC jitter\n\n### Run everything\n\nFrom the repository root:\n\n```sh\n./scripts/benchmarks/run-benchmarks.sh\n```\n\nThe script builds the TypeScript packages and an **optimized (release) sidecar**, points the SDK at it via `AGENT_OS_SIDECAR_BIN`, and writes one JSON result per benchmark to `scripts/benchmarks/results/`:\n\n| Result file | Feeds marketing input |\n|---|---|\n| `coldstart-sleep.json` | `COLDSTART_P50/P95/P99_MS` |\n| `memory-sleep.json` | `MEMORY_SHELL_MB` (`result.avgPerVmRssBytes / 1024²`) |\n| `memory-pi-session.json` | `MEMORY_AGENT_MB` (`result.avgPerVmRssBytes / 1024²`) |\n\nCopy those numbers into `website/src/data/bench.ts`; every figure and multiplier on this page recomputes from them.\n\n### Run a single benchmark\n\nEach benchmark is a standalone `tsx` entrypoint. Build first (`pnpm build` and `cargo build --release -p agent-os-sidecar`), then:\n\n```sh\nexport AGENT_OS_SIDECAR_BIN=\"$PWD/target/release/agent-os-sidecar\"\n\n# Cold start (sleep workload)\npnpm exec tsx scripts/benchmarks/coldstart.bench.ts --workload=sleep --iterations=2000\n\n# Memory — simple shell command\npnpm exec tsx --expose-gc scripts/benchmarks/memory.bench.ts --workload=sleep --count=20\n\n# Memory — full coding agent\npnpm exec tsx --expose-gc scripts/benchmarks/memory.bench.ts --workload=pi-session --count=10\n```\n\nJSON goes to stdout; a human-readable table and progress go to stderr.\n\n### Sample sizes\n\nPercentiles are nearest-rank: `sorted[ceil(p/100 · n) − 1]`. With too few samples the tail is meaningless — at `n = 30`, **p99 is literally the single slowest run** and p95 is the second slowest. Use enough iterations that the reported percentile is averaged over real tail samples, not one outlier:\n\n| Statistic | Minimum iterations |\n|---|--:|\n| p50 (median) | ~30 |\n| p95 | ~200 |\n| p99 | ~1,000 |\n\n`run-benchmarks.sh` uses `--iterations=2000` for cold start so p95/p99 are trustworthy. Memory per VM is a mean of per-VM step deltas with low variance, so `--count=20` (shell) / `--count=10` (agent) is sufficient.\n\n> The `pi-session` memory workload needs a working in-VM agent runtime (the Pi adapter process must launch inside the VM). On builds where the agent runtime is unavailable it will fail its process check rather than report a number.\n\n### Methodology\n\nEvery benchmark **creates the sidecar once up front** (`AgentOs.createSidecar()`) and leases all VMs from it. VMs are **incremental tenants of one shared sidecar process** — not one process each — so the figures measure the marginal cost of a VM, not a fresh process. (`AgentOs.create()` with no `sidecar` option already uses the shared `default`-pool sidecar, so this is the default everywhere, including RivetKit actors.)\n\nBefore any measured iteration, each benchmark does a **cold run** — a throwaway VM that is created, started, and (for cold start) snapshotted. This pays the one-time process spawn + bootstrap so the recorded numbers reflect the warm, steady-state incremental per-VM cost, never the first VM. The release sidecar is required: a debug build is several times slower and inflates the numbers.","src/content/docs/docs/benchmarks.mdx","8b389f84726bb5c2","docs/bindings",{"id":52,"data":54,"body":57,"filePath":58,"digest":59,"deferredRender":16},{"title":55,"description":56,"skill":16},"Bindings","Expose custom host functions to agents as CLI commands inside the VM.","Expose your host JavaScript functions (defined with Zod input schemas) to agents as auto-generated CLI commands installed at `/usr/local/bin/agentos-{name}` inside the VM, injected into the agent's [system prompt](/docs/system-prompt) and callable inside scripts for code-mode token savings.\n\n## Getting started\n\nDefine a bindings group with Zod input schemas and pass it to `agentOS()`. Each binding becomes a CLI command inside the VM.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/bindings/server.ts\" />\n\n\u003CCodeSnippet file=\"examples/bindings/client.ts\" />\n\u003C/CodeGroup>\n\nEach binding can set an explicit `timeout` (in milliseconds) for long-running work. Bindings run without a timeout unless one is set.\n\n### Zod to CLI mapping\n\nZod schema fields are converted to CLI flags automatically. Field names are converted from camelCase to kebab-case.\n\n| Zod type | CLI syntax | Example |\n|---|---|---|\n| `z.string()` | `--name value` | `--path /tmp/out.png` |\n| `z.number()` | `--name 42` | `--limit 5` |\n| `z.boolean()` | `--flag` / `--no-flag` | `--full-page` |\n| `z.enum([\"a\",\"b\"])` | `--name a` | `--format json` |\n| `z.array(z.string())` | `--name a --name b` | `--tags foo --tags bar` |\n\nOptional fields (via `.optional()`) become optional flags. Required fields are enforced at validation time. Use `.describe()` on Zod fields to generate useful `--help` output.\n\n### What the agent sees\n\nWhen bindings are registered, CLI shims are installed at `/usr/local/bin/agentos-{name}` inside the VM and the binding list is injected into the agent's [system prompt](/docs/system-prompt), so keep binding descriptions concise to save tokens.\n\nThe agent interacts with bindings as shell commands:\n\nThe listing subcommand is still named `list-tools` for CLI compatibility.\n\n```bash\n# List all available bindings groups\nagentos list-tools\n\n# List bindings in a specific group\nagentos list-tools weather\n\n# Get help for a binding\nagentos-weather forecast --help\n\n# Call a binding with flags\nagentos-weather forecast --city Paris --days 3\n\n# Call a binding with inline JSON\nagentos-weather forecast --json '{\"city\":\"Paris\",\"days\":3}'\n\n# Call a binding with JSON from a file\nagentos-weather forecast --json-file /tmp/input.json\n```\n\nOn success, the binding exits `0` and writes a JSON envelope to stdout:\n\n```json\n{\"ok\":true,\"result\":{\"temperature\":22,\"condition\":\"sunny\"}}\n```\n\nOn failure (validation or execution error), the binding exits non-zero and writes the error message to stderr:\n\n```text\nMissing required flag: --city\n```\n\n## Bindings vs MCP servers\n\nagentOS supports two ways to give agents access to external functionality: **bindings** and **MCP servers**. Both work, but they have different tradeoffs.\n\n| | Bindings | MCP Servers |\n|---|---|---|\n| **How it works** | Call JavaScript functions on the host directly | Connect to a standard MCP server |\n| **Authentication** | None required. Direct binding to the agent's OS. | Requires custom auth configuration per server |\n| **Code mode** | Built-in. Bindings are exposed as CLI commands, so agents can call them inside scripts for up to 80% token reduction. | Requires extra work to make code mode work out of the box |\n| **Latency** | Near-zero. Bound directly to the host process. | Extra network hop to reach the MCP server |\n| **Setup** | Define bindings in your actor code with Zod schemas | Configure any standard MCP server |\n\nUse bindings when you want to expose your own JavaScript functions to agents. Use MCP servers when you want to connect to existing third-party services. See [Sessions](/docs/sessions#createsession-options) for MCP server configuration.\n\n## Security\n\nBinding calls from the agent securely invoke your `execute()` functions on the host. Your functions run with full access to the host environment, so you can call databases, APIs, and services directly without proxying credentials into the VM. The agent never sees the credentials, it only sees the binding's input/output contract.\n\nBindings run on the host with full access to the host environment, so do not expose bindings that could compromise the host without appropriate safeguards.","src/content/docs/docs/bindings.mdx","a88d54a3c6eadec8","docs/core",{"id":60,"data":62,"body":65,"filePath":66,"digest":67,"deferredRender":16},{"title":63,"description":64,"skill":16},"Core Package","Use @rivet-dev/agentos-core standalone for direct VM control without the Rivet Actor runtime.","## agentOS vs agentOS Core\n\nThe `agentOS()` actor (from `@rivet-dev/agentos`) wraps the core package and adds:\n\n| | Core (`@rivet-dev/agentos-core`) | Actor (`@rivet-dev/agentos`) |\n|-|---|---|\n| Persistence | In-memory by default (pluggable via [mounts](#mounts)) | Persistent filesystem and sessions |\n| Distributed state | Manage yourself | Built-in distributed statefulness |\n| Stateful VMs | Complex to run yourself | Built into Rivet |\n| Sleep/wake | Manual `dispose()` / `create()` | Automatic |\n| Events | Direct callbacks | Broadcasted to all connected clients |\n| Preview URLs | None | Built-in signed URL server |\n| Multiplayer | N/A | Multiple clients on same actor |\n| Orchestration | N/A | Workflows, queues, cron |\n| Agent-to-agent communication | Custom | Built into [Rivet Actors](/docs/agent-to-agent) |\n| Authentication | Set up yourself | [Documentation](/docs/authentication) |\n\nWe recommend using [Rivet Actors](https://rivet.dev/docs/actors) because they provide a portable way to run `agentOS()` on any infrastructure with built-in persistence, networking, and orchestration. Use the core package if you need the most bare-bones implementation possible.\n\n## Install\n\n```bash\nnpm install @rivet-dev/agentos-core\n```\n\n## Boot a VM\n\nDefine the actor on the server:\n\n\u003CCodeSnippet file=\"examples/core/server.ts\" title=\"server.ts\" />\n\nThen drive it from a typed client:\n\n\u003CCodeSnippet file=\"examples/core/client.ts\" region=\"boot\" title=\"client.ts\" />\n\n## Sidecar process\n\nEvery VM runs inside a **shared sidecar process** rather than a process of its own. By default all VMs are tenants of a single, process-global sidecar (the `default` pool), so each additional VM only adds its marginal cost — a V8 isolate plus its kernel state — instead of a whole OS process. This is what keeps per-VM memory in the tens of MB and warm VM creation in the single-digit milliseconds (see [Benchmarks](/docs/benchmarks)).\n\nThis is automatic — `agentOS()` and `AgentOs.create()` use the shared default sidecar with no configuration, and the same applies to Rivet Actors (each actor's VM is a tenant of the shared process). Disposing a VM tears down only that VM; the shared sidecar process is reused across VMs and stays alive for the lifetime of the host process.\n\nFor advanced cases the core package exposes explicit sidecar handles so you can isolate a group of VMs in their own process:\n\n\u003CCodeSnippet file=\"examples/core/advanced.ts\" title=\"advanced.ts\" />\n\n## Filesystem\n\n\u003CCodeSnippet file=\"examples/core/client.ts\" region=\"filesystem\" title=\"client.ts\" />\n\n## Processes\n\nLong-running process output is delivered over the live `processOutput` / `processExit` events on a connection rather than per-pid callbacks:\n\n\u003CCodeSnippet file=\"examples/core/client.ts\" region=\"processes\" title=\"client.ts\" />\n\n## Agent sessions\n\n`createSession` returns a session record. All session operations take its `sessionId`. Session events and permission requests are delivered over the live connection (`sessionEvent` / `permissionRequest`):\n\n\u003CCodeSnippet file=\"examples/core/client.ts\" region=\"sessions\" title=\"client.ts\" />\n\nSubscribe to `sessionEvent` before sending a prompt so you do not miss the live stream. Persisted history can be read back later with `getSessionEvents()`.\n\n## Networking\n\n\u003CCodeSnippet file=\"examples/core/client.ts\" region=\"networking\" title=\"client.ts\" />\n\n## Cron jobs\n\nCron jobs run an `\"exec\"` command or a `\"session\"` prompt on a schedule. Fired jobs are surfaced over the live `cronEvent` connection:\n\n\u003CCodeSnippet file=\"examples/core/client.ts\" region=\"cron\" title=\"client.ts\" />\n\n## Mounts\n\nConfigure filesystem backends at boot time.\n\nNative mount plugins (host directories, S3, etc.) are passed via `plugin`, each\nidentified by an `id` and a `config` object.\n\n\u003CCodeSnippet file=\"examples/core/mounts.ts\" title=\"server.ts\" />\n\n## `agentOS()` configuration reference\n\nWhen you use the [`agentOS()` actor](/docs/quickstart), all VM configuration is passed to the factory as a single flat object. This is the consolidated config block to copy and adapt:\n\n\u003CCodeSnippet file=\"examples/core/config-reference.ts\" title=\"server.ts\" />\n\nThe top-level fields are documented inline above. See [Mounts](#mounts), [Software](/docs/software), and (for the hooks) [Approvals](/docs/approvals).\n\n### Lifecycle hooks\n\n`onPermissionRequest(sessionId, request)` fires when an agent requests permission. `onSessionEvent(sessionId, event)` is a server-side hook called once for every session event: unlike the client-side `sessionEvent` connection subscription, it runs in the actor for every event regardless of connected clients, making it the place for server-side logging, persistence, or side effects.\n\n\u003CCodeSnippet file=\"examples/core/hooks.ts\" title=\"server.ts\" />\n\n### Timeouts\n\n| Setting | Default | Description |\n|---------|---------|-------------|\n| Action timeout | 15 minutes | Maximum time for any single action |\n| Sleep grace period | 15 minutes | Time before sleeping after all activity stops |\n\nThese are set internally by the `agentOS()` factory and cannot be overridden per-call. See [Persistence & Sleep](/docs/persistence) for details on the sleep lifecycle.","src/content/docs/docs/core.mdx","f81f7b073000e086","docs/cost-evaluation",{"id":68,"data":70,"body":73,"filePath":74,"digest":75,"deferredRender":16},{"title":71,"description":72,"skill":16},"Cost Evaluation","How agentOS compares on cost to per-second sandbox providers when you run coding-agent VMs on your own hardware.","agentOS is a library you run on hardware you already control, not a metered service. That changes the cost model for running coding-agent VMs from \"pay a provider per sandbox-second\" to \"pay for the compute you provision, then pack as much work onto it as it can hold.\" This page explains where the savings come from and how to reason about them honestly. It does not publish a single magic multiplier, because the real number depends on your workload, your hardware, and how you share VMs.\n\n\u003CNote>For measured latency (cold start, warm execution, and reuse fast paths), see [Benchmarks](/docs/benchmarks). This page is about cost structure, not raw performance.\u003C/Note>\n\n## Where the savings come from\n\nTwo structural differences drive the cost gap versus per-second sandbox providers:\n\n- **You run on your own hardware**: you choose the cloud, instance type, architecture, and region. A small commodity instance (for example an ARM VM from a budget host) costs a flat hourly or monthly rate that is typically far below what per-sandbox-second billing adds up to once you have steady agent traffic. You also avoid egress fees and vendor lock-in.\n- **You decide the isolation granularity**: sandbox providers bill a full container or microVM per execution, usually with a minimum memory reservation that you pay for even when your code uses a fraction of it. With agentOS you own the VM lifecycle: you can dedicate a VM per tenant or per task for maximum isolation, or amortize setup by reusing one VM across many runs.\n\n## The isolation model matters for cost\n\nEach `AgentOs.create()` boots a fully virtualized VM, and each `exec()` / `run()` inside it is a fresh guest process. That gives you a dial between isolation and density:\n\n- **One VM per task or tenant (strongest isolation)**: create a VM, run the work, and dispose it, or give each tenant its own VM. Each VM is its own crash and resource domain, with the highest per-VM overhead. Best when load is untrusted or bursty.\n- **A shared VM for trusted work**: reuse one VM across many runs to amortize the VM boot cost. Each `exec()` / `run()` still executes in a fresh guest process, so in-memory state does not leak between runs, but the VM and filesystem are shared. Good for trusted, sequential work.\n\nThe denser you can safely pack agent work onto an instance, the lower your effective cost per execution. See [Resource Limits](/docs/resource-limits) for the per-VM caps that govern how densely you can pack, and [Processes & Shell](/docs/processes) for how guest processes run inside a VM.\n\n## How to estimate your own cost\n\nBecause agentOS runs on hardware you provision, the honest way to compare is to plug in your own numbers:\n\n1. **Pick your hardware and its rate**: take the hourly or monthly price of the instance you would run on, and divide down to a per-second instance cost.\n2. **Estimate how many concurrent VMs fit**: measure per-VM memory overhead on your target hardware under your isolation strategy, then divide your usable RAM by that figure. Leave headroom (the measurement and any orchestration layer will not bin-pack perfectly).\n3. **Divide instance cost by concurrent VMs**: that gives a cost-per-VM-second you can compare against a provider's per-sandbox-second rate.\n\n\u003CTip>Measure on the hardware and isolation strategy you will actually deploy. Per-VM overhead depends on whether you create a fresh VM per task or reuse one across runs, and on the work the agent does, so a number measured on one machine will not transfer cleanly to another.\u003C/Tip>\n\n## Comparing against sandbox providers\n\nWhen you do compare against a per-second sandbox provider, hold the methodology honest:\n\n- **Sandbox cost** is the provider's minimum allocatable memory times their per-GiB-second rate (plus any egress and platform fees). The minimum reservation is the floor you pay even for tiny workloads.\n- **agentOS cost** is your instance cost per second divided by the number of VMs you can keep live on it, with realistic headroom for bin-packing inefficiency.\n\nThe advantage is largest for **many small, short executions**, where a per-sandbox minimum reservation dominates and your own hardware lets you pack densely. It narrows for **heavyweight, long-lived workloads** (for example dev servers that need hundreds of megabytes regardless), where the win shifts from density to hardware choice: you still avoid per-second metering, egress fees, and lock-in, but the raw memory-density advantage is smaller.\n\n| Workload | Primary cost advantage |\n| --- | --- |\n| Many small, short executions | Density: pack many VMs per instance, no per-sandbox minimum |\n| Heavyweight, long-lived workloads | Hardware choice, flat instance pricing, no egress or lock-in |\n| High concurrency | Reuse a VM across runs to amortize VM boot cost |\n\n\u003CWarning>Be careful generalizing cost ratios from a single benchmark. Provider pricing, instance pricing, and exchange rates change over time, and per-VM overhead varies by workload and isolation strategy. Re-measure on your own hardware before quoting a number.\u003C/Warning>\n\nWhen you do need a full Linux sandbox for heavier agent workloads, see [agentOS vs Sandbox](/docs/versus-sandbox) for how the two models combine.","src/content/docs/docs/cost-evaluation.mdx","d061affd8af70768","docs/crash-course",{"id":76,"data":78,"body":81,"filePath":82,"digest":83,"deferredRender":16},{"title":79,"description":80,"skill":16},"Crash Course","Run coding agents inside isolated VMs with full filesystem, process, and network control.","\u003CNote>\nagentOS is in preview and the API is subject to change. If you run into issues, please [report them on GitHub](https://github.com/rivet-dev/rivet/issues) or [join our Discord](https://rivet.dev/discord).\n\u003C/Note>\n\n{/* SKILL_OVERVIEW_START */}\n\n## When to Use agentOS\n\n- **Coding agents**: Run any coding agent with full OS access, file editing, shell execution, and tool use.\n- **Automated pipelines**: CI-like workflows where agents clone repos, fix bugs, run tests, and open PRs.\n- **Multi-agent systems**: Coordinators dispatching to specialized agents, review pipelines, planning chains.\n- **Scheduled maintenance**: Cron-based agents that audit code, update dependencies, or generate reports.\n- **Collaborative workspaces**: Multiple users observing and interacting with the same agent session in realtime.\n\n## Minimal Project\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/minimal-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\nAfter the quickstart, customize your agent with the [Registry](/registry).\n\n## Agents\n\n### Sessions & Transcripts\n\nCreate agent sessions, send prompts, and stream responses in realtime. Transcripts are persisted automatically across sleep/wake cycles.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/sessions-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/sessions)*\n\n### Approvals\n\nApprove or deny agent tool use with human-in-the-loop patterns or auto-approve for trusted workloads.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/permissions-server.ts\" title=\"server.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/permissions-client.ts\" title=\"client.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/approvals)*\n\n### Bindings\n\nExpose your JavaScript functions to agents as CLI commands inside the VM. Each binding group becomes a binary at `/usr/local/bin/agentos-{name}`, and each binding becomes a subcommand with flags auto-generated from its Zod input schema. The server below defines a `weather` binding group with a `forecast` binding; the client opens a session and prompts the agent, which calls the binding itself as a shell command.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/bindings/server.ts\" title=\"server.ts\" />\n\n\u003CCodeSnippet file=\"examples/bindings/client.ts\" title=\"client.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/bindings) or [Documentation](/docs/bindings)*\n\n### Agent-to-Agent\n\nLet one agent call another through a [binding](/docs/bindings). The coder gets a `review` binding it invokes itself, which bridges into the reviewer's isolated VM.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/agent-to-agent-server.ts\" title=\"server.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/agent-to-agent-client.ts\" title=\"client.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/agent-to-agent)*\n\n### Multiplayer & Realtime\n\nConnect multiple clients to the same agent VM. All subscribers see session output, process logs, and shell data in realtime.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/multiplayer-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/multiplayer)*\n\n### Workflows\n\nOrchestrate multi-step agent tasks with durable workflows that survive crashes and restarts.\n\n\u003CCodeSnippet file=\"examples/crash-course/workflows.ts\" />\n\n[Documentation](/docs/workflows)\n\n## Operating System\n\n### Filesystem\n\nRead, write, and manage files inside the VM. The `/home/agentos` directory is persisted automatically across sleep/wake cycles.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/filesystem-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/filesystem)*\n\n### Processes & Shell\n\nExecute commands, spawn long-running processes, and open interactive shells.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/processes-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/processes)*\n\n### Networking & Previews\n\nProxy HTTP requests into VMs with `vmFetch`. Create preview URLs for port forwarding VM services to shareable public URLs.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/networking-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/networking)*\n\n### Cron Jobs\n\nSchedule recurring commands and agent sessions with cron expressions.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/cron-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/cron)*\n\n### Sandbox Mounting\n\nagentOS uses a hybrid model: agents run in a lightweight VM by default and mount a full sandbox on demand for heavy workloads like browsers, compilation, and desktop automation. Sandboxes are powered by [Sandbox Agent](https://sandboxagent.dev), so you can swap providers without changing agent code. Mount the sandbox as a filesystem and expose its process management as bindings.\n\n\u003CCodeSnippet file=\"examples/crash-course/sandbox.ts\" />\n\n[Documentation](/docs/sandbox)\n\n{/* SKILL_OVERVIEW_END */}","src/content/docs/docs/crash-course.mdx","c813a3d461363a4e","docs/cron",{"id":84,"data":86,"body":89,"filePath":90,"digest":91,"deferredRender":16},{"title":87,"description":88,"skill":16},"Cron Jobs","Schedule recurring commands and agent sessions in agentOS VMs.","Schedule recurring work with cron expressions, running either a shell command (`exec`) or an agent session (`session`), with overlap modes (`allow`, `skip`, `queue`) and `cronEvent` streaming to monitor execution. Cron jobs keep the actor alive while a job runs; the actor can sleep between executions.\n\n## Schedule a command\n\nRun a shell command on a recurring schedule. Pass a custom `id` to make a job easier to manage and cancel later.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/cron/schedule-command.ts\" />\n\u003CCodeSnippet file=\"examples/cron/server.ts\" />\n\u003C/CodeGroup>\n\n## Schedule an agent session\n\nCreate a recurring agent session that runs a prompt on a schedule.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/cron/schedule-session.ts\" />\n\u003CCodeSnippet file=\"examples/cron/server.ts\" />\n\u003C/CodeGroup>\n\n## Overlap modes\n\nControl what happens when a cron job triggers while a previous execution is still running.\n\n| Mode | Behavior |\n|------|----------|\n| `\"skip\"` | Skip this trigger if the previous run is still active |\n| `\"allow\"` | Allow concurrent executions (default) |\n| `\"queue\"` | Queue this trigger and run it after the previous one finishes |\n\nPrefer `\"skip\"` for most jobs to avoid unbounded concurrency if a run takes longer than the interval. Use `\"queue\"` when every trigger must eventually execute.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/cron/overlap.ts\" />\n\u003CCodeSnippet file=\"examples/cron/server.ts\" />\n\u003C/CodeGroup>\n\n## Monitor cron events\n\nSubscribe to the `cronEvent` event to track job execution. It is emitted whenever a cron job runs, carrying a single payload field:\n\n- **`data.event`**: A `CronEvent` describing the run.\n\n\u003CCodeSnippet file=\"examples/cron/monitor.ts\" region=\"subscribe\" />\n\nSubscribe before scheduling so you do not miss early runs.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/cron/monitor.ts\" />\n\u003CCodeSnippet file=\"examples/cron/server.ts\" />\n\u003C/CodeGroup>\n\n## List and cancel cron jobs\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/cron/list-cancel.ts\" />\n\u003CCodeSnippet file=\"examples/cron/server.ts\" />\n\u003C/CodeGroup>\n\n## Example: Heartbeat pattern\n\nSchedule a recurring agent session to periodically check on a task. This is the core pattern behind [OpenClaw](https://openclaw.org), where an agent wakes up on a schedule to review progress, take action, and go back to sleep.\n\n\u003CCodeSnippet file=\"examples/cron/heartbeat.ts\" region=\"heartbeat\" />\n\nThe agent sleeps between executions and only consumes resources when the cron job fires.","src/content/docs/docs/cron.mdx","91a73249b3cb5d29","docs/debugging",{"id":92,"data":94,"body":97,"filePath":98,"digest":99,"deferredRender":16},{"title":95,"description":96},"Debugging","Capture agent logs and runtime (sidecar) logs to diagnose sessions, tool calls, and crashes.","Two log streams help diagnose what's happening inside a VM: the **agent's** own output and the **runtime (sidecar)** logs.\n\n## Agent logs (`onAgentStderr`)\n\nThe coding agent (ACP adapter) runs as a process inside the VM and uses **stdout for the ACP protocol**, so its **stderr** carries the agent's logs, warnings, and crash output — the first place to look when a tool call or session fails mid-turn. Capture it with `onAgentStderr` on the VM:\n\n\u003CCodeSnippet file=\"examples/debugging/agent-logs.ts\" />\n\nIt's a VM-level option covering every session's agent process; if omitted, chunks are written to the host `process.stderr` by default. See [Sessions → Agent logs](/docs/sessions#agent-logs).\n\n## Agent crashes (`onAgentExit`)\n\nIf the agent process exits without `closeSession()`, the runtime logs the exit, **auto-restarts the agent** (bounded to 3 restarts per session, re-attaching the same session id when the agent supports native resume), and fires `onAgentExit` with the outcome:\n\n```ts\nconst agentOs = await AgentOs.create({\n software: [pi],\n onAgentExit(event) {\n // event: { sessionId, agentType, processId, pid, exitCode,\n // restart: \"restarted\" | \"unsupported\" | \"failed\" | \"exhausted\",\n // restartCount, maxRestarts }\n console.warn(`agent exited (code ${event.exitCode}), restart=${event.restart}`);\n },\n});\n```\n\nOnly `restart === \"restarted\"` leaves the session usable; every other outcome means the session was evicted. The crash *reason* is on the agent's stderr (above); the exit event tells you it died and whether it recovered. See [Sessions → Agent crashes and auto-restart](/docs/sessions#agent-crashes-and-auto-restart).\n\n## Runtime logs (sidecar)\n\nThe agentOS sidecar emits structured **logfmt** logs for request handling, networking, and lifecycle. Configure them with environment variables on the **host process** (the sidecar inherits the host environment):\n\n| env var | effect |\n|---------|--------|\n| `AGENTOS_LOG_LEVEL` / `LOG_LEVEL` / `RUST_LOG` | log filter, in that priority. Uses [EnvFilter](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html) syntax, e.g. `debug`, `info`, `agentos_sidecar=debug,info`. Default `info`. |\n| `RUST_LOG_FORMAT` | `logfmt` (default) or `text` |\n| `AGENTOS_LOG_FILE` | append logs to this file instead of stderr (never stdout, which carries the wire protocol) |\n| `RUST_LOG_{SPAN_NAME,SPAN_PATH,TARGET,LOCATION,MODULE_PATH,ANSI_COLOR}` | per-field toggles (`=1` to enable) |\n\n```bash\nAGENTOS_LOG_LEVEL=debug AGENTOS_LOG_FILE=./sidecar.log RUST_LOG_FORMAT=logfmt node app.mjs\n```\n\nProduces logfmt lines such as:\n\n```text\nts=2026-… level=info message=\"ext request received\" kind=create_session\nts=2026-… level=info message=\"ext request handled\" kind=create_session elapsed_ms=1798\nts=2026-… level=debug message=\"querying: api.anthropic.com. A\"\n```\n\n\u003CNote>\nMost sidecar log activity is on the session/ACP path. A bare `AgentOs.create()` or a single `exec()` emits almost nothing — create a session (and send a prompt) to see request-handling logs.\n\u003C/Note>\n\nUse **agent logs** to see what the agent did (tool calls, model errors), and **runtime logs** to see what the sidecar did around it (request timing, DNS, lifecycle).","src/content/docs/docs/debugging.mdx","f006ad8031df8198","docs/deployment",{"id":100,"data":102,"body":105,"filePath":106,"digest":107,"deferredRender":16},{"title":103,"description":104,"skill":16},"Deploy","Choose the right deployment option for agentOS.","import DeployTargets from '../../../components/DeployTargets.astro';\n\nagentOS is powered by [Rivet](https://rivet.dev), an open-source actor platform, and runs as Rivet Actors. Three ways to run it in production:\n\n- **[Rivet Cloud](https://rivet.dev/cloud)**: fully managed (Rivet Compute, or bring your own cloud). Zero-ops.\n- **Self-hosted**: run the open-source Rivet platform on your own infrastructure (Kubernetes, Hetzner, VMs, and more) for full control.\n- **[agentOS Core](/docs/core)**: embed `@rivet-dev/agentos-core` directly in any Node.js backend, no platform required.\n\nPick a deploy target below, or see [Rivet's deployment guides](https://rivet.dev/docs/deploy/).\n\n## Deploy targets\n\nSee the [Rivet deploy docs](https://rivet.dev/docs/deploy/) for the full list. Available targets:\n\n\u003CDeployTargets />\n\n## Enterprise support\n\nEnterprise support and managed deployment are available, including dedicated support, custom SLAs, and compliance reviews. [Contact the Rivet team](https://rivet.dev/sales) to discuss your requirements.","src/content/docs/docs/deployment.mdx","2e9d172a6723589c","docs/filesystem",{"id":108,"data":110,"body":113,"filePath":114,"digest":115,"deferredRender":16},{"title":111,"description":112,"skill":16},"Filesystem","Read, write, mount, and manage files inside agentOS, all backed by a virtual filesystem isolated from the host disk.","Each VM has its own filesystem that the agent works in. Guest `fs` calls never touch the host disk, and it persists automatically across sleep/wake with no setup. See [Persistence](/docs/persistence) for the details.\n\n## Mounts\n\nBack a guest path with external storage by adding it to the `mounts` config. Each mount takes a `path` and an optional `readOnly` flag, and the guest only ever sees the mounted subtree, never the wider host.\n\n\u003CTabs>\n\n\u003CTab title=\"Host directory\">\n\nProject a real host directory into the filesystem, Docker-style. The guest sees only the mounted subtree, never the wider host filesystem. Path-escape attempts (symlinks, `..`, path aliasing) are confined to the mount root.\n\n\u003CCodeSnippet file=\"examples/filesystem/mount-host-dir.ts\" />\n\n\u003C/Tab>\n\n\u003CTab title=\"S3\">\n\nMount an S3 bucket with the built-in `s3` plugin. Pass an optional `prefix` to scope storage to a key path within the bucket, useful for sharing one bucket across multiple agents.\n\nThe backend is a block store, not a one-object-per-file mapping: file contents are split into fixed-size chunks (4 MB by default) stored as individual S3 objects, with a separate metadata layer mapping each file to its chunks. This keeps large files, partial reads and writes, and snapshots efficient without rewriting whole objects.\n\n\u003CCodeSnippet file=\"examples/filesystem/mount-s3.ts\" />\n\nThe `s3` plugin config also accepts `credentials` (`{ accessKeyId, secretAccessKey }`) and a custom `endpoint` for S3-compatible providers.\n\n\u003C/Tab>\n\n\u003CTab title=\"Google Drive\">\n\nMount a Google Drive folder with the built-in `google_drive` plugin.\n\n\u003CCodeSnippet file=\"examples/filesystem/mount-google-drive.ts\" />\n\n\u003C/Tab>\n\n\u003CTab title=\"In-memory\">\n\nUse the built-in `memory` plugin for an ephemeral mounted directory in the native RivetKit `agentOS()` actor.\n\n\u003CCodeSnippet file=\"examples/filesystem/server.ts\" />\n\n\u003C/Tab>\n\n\u003CTab title=\"Custom JS VFS\">\n\nUse `mountFs()` for a callback-backed JS filesystem driver. The driver must live in the same JS process as the `AgentOs` instance, such as direct core usage or a custom RivetKit actor that owns an `AgentOs` instance.\n\n\u003CCodeSnippet file=\"examples/filesystem/mount-custom-vfs.ts\" />\n\nThe native `agentOS()` actor cannot accept `{ driver }` mounts in config because JS callback objects are not serializable across the native plugin boundary. Use `plugin` mounts there.\n\n\u003C/Tab>\n\n\u003C/Tabs>\n\n## File operations\n\nThese operations are primarily what the agent uses inside the VM, and are also available from the client to seed inputs and read results. For large or read-only inputs (a repo, a dataset), a read-only [host mount](#mounts) is faster than copying files in. Programs that need stdin or live output use exec instead (see [Core](/docs/core)).\n\n### Read and write\n\n\u003CCodeSnippet file=\"examples/filesystem/operations.ts\" region=\"read-write\" />\n\n### Batch read and write\n\n\u003CCodeSnippet file=\"examples/filesystem/operations.ts\" region=\"batch\" />\n\n### Directories\n\n\u003CCodeSnippet file=\"examples/filesystem/operations.ts\" region=\"directories\" />\n\n### File metadata\n\n\u003CCodeSnippet file=\"examples/filesystem/operations.ts\" region=\"metadata\" />\n\n### Move and delete\n\n\u003CCodeSnippet file=\"examples/filesystem/operations.ts\" region=\"move-delete\" />\n\n## Permissions\n\nFilesystem access is governed by the VM permission policy. The filesystem scope is granted by default; restrict it by path, for example to deny a sensitive directory:\n\n```ts\nconst vm = agentOS({\n permissions: {\n fs: {\n default: \"allow\",\n rules: [{ mode: \"deny\", operations: [\"*\"], paths: [\"/home/agentos/secrets/**\"] }],\n },\n },\n});\n```\n\nSee [Permissions](/docs/permissions) for the full configuration.\n\n## Sandboxes\n\nFor heavier or untrusted workloads, run a full Linux [sandbox](/docs/sandbox) alongside the VM and mount its filesystem into agentOS. The agent then reads and writes the sandbox's files through the same `fs` APIs while the sandbox handles execution. See [Sandbox Mounting](/docs/sandbox) for setup.\n\n## Default layout\n\nWith no `mounts` configured, every VM boots an Alpine-based root filesystem with the standard POSIX directories:\n\n- `/home/agentos`: the agent's home directory (`$HOME`) and default working directory (`pwd`) when spawned, where it reads and writes (mounts land under it, e.g. `/home/agentos/data`).\n- `/bin`, `/sbin`, `/usr`: installed commands (common POSIX utilities by default, plus any [software](/docs/software) you add).\n- `/etc`, `/lib`, `/opt`, `/root`, `/run`, `/srv`, `/tmp`, `/var`, `/mnt`: standard system paths.\n\nIt is backed by the VM's own filesystem and persisted across sleep/wake. Nothing comes from or touches the host disk.","src/content/docs/docs/filesystem.mdx","2cd99eb340296d97",{"id":9,"data":117,"body":119,"filePath":120,"digest":121,"deferredRender":16},{"title":118,"description":80},"Introduction","agentOS runs coding agents inside isolated VMs with full filesystem, process, and\nnetwork control — a lightweight VM in your own process with bindings, permissions,\nand orchestration built in.\n\n\u003CCardGroup>\n \u003CCard title=\"Quickstart\" href=\"/docs/quickstart\">\n Boot a VM and run your first coding agent.\n \u003C/Card>\n \u003CCard title=\"Crash Course\" href=\"/docs/crash-course\">\n Learn the core agentOS concepts.\n \u003C/Card>\n \u003CCard title=\"Agents\" href=\"/docs/agents/pi\">\n Run Pi, Claude Code, Codex, and OpenCode.\n \u003C/Card>\n\u003C/CardGroup>","src/content/docs/docs/index.mdx","df667a889276d10c","docs/js-runtime",{"id":122,"data":124,"body":127,"filePath":128,"digest":129,"deferredRender":16},{"title":125,"description":126,"skill":16},"JavaScript Runtime","How agentOS runs guest JavaScript: native V8 acceleration, low memory overhead, and Node.js compatibility.","The JavaScript runtime is powered by the Rivet [Secure Exec](https://secureexec.dev) project, which provides the isolated V8 runtime that agentOS runs guest code in. Every guest VM executes its JavaScript inside this runtime, fully sandboxed from the host.\n\n## JavaScript Acceleration\n\n- **JavaScript is unusually slow as WebAssembly**: unlike most software, JavaScript pays a heavy penalty when compiled to WebAssembly, because so much engineering has gone into JIT-compiling JavaScript directly in V8.\n- **Native V8, full JIT**: agentOS therefore runs guest JavaScript on the native V8 engine with its full JIT compiler, not through a WASM translation layer. We call this **JavaScript Acceleration**.\n- **Native-speed execution**: guest JavaScript runs at native speed while staying inside the isolation boundary, with normal Node.js semantics.\n\n## Comparison to Node.js efficiency\n\n- **Isolate model, not processes**: agentOS runs each agent inside a V8 isolate rather than spawning a full Node.js process per agent.\n- **Low memory overhead**: an isolate carries far less per-agent memory overhead than a full Node.js process, so many agents fit in the footprint that a process-per-agent model would spend on a handful.\n- **Benchmarks**: see the Secure Exec [benchmarks](https://secureexec.dev/docs/benchmarks) for cold start, warm execution, and reuse measurements.\n\n## Node.js compatibility\n\nGuest code runs as Node.js (reporting `process.version` as `v22.0.0`), but it never touches the host runtime. Every `node:` builtin resolves to a kernel-backed bridge or an in-isolate polyfill, never the real host module. For the full builtin matrix (`fs`, `net`, `http`, `crypto`, undici-backed `fetch`, and more), see the Secure Exec [Node.js Compatibility](https://secureexec.dev/docs/nodejs-compatibility) reference.\n\n### npm packages\n\nBy default the VM has no npm packages installed. Mount a host `node_modules` directory to give guest code access to real packages: the `nodeModulesMount` helper projects it read-only at `/root/node_modules`, and the in-kernel resolver walks it exactly like Node.js does, with no bundling or patching.\n\n\u003CCodeSnippet file=\"examples/js-runtime/node-modules-mount.ts\" />\n\nResolution matches naive Node.js over the mounted tree: the ancestor `node_modules` walk, `package.json` `exports`/`imports` and conditions, and `realpath`/symlink following (so pnpm and yarn layouts resolve too). Both ESM `import` and CommonJS `require` work. See the Secure Exec [module loading](https://secureexec.dev/docs/features/module-loading) guide for the full model.","src/content/docs/docs/js-runtime.mdx","bb45b5b55c2c8195","docs/limitations",{"id":130,"data":132,"body":135,"filePath":136,"digest":137,"deferredRender":16},{"title":133,"description":134,"skill":16},"Limitations","What the agentOS VM does not support, and how to work around it.","agentOS is a Linux environment with a POSIX-compliant virtual kernel. It handles most agent workloads (coding, scripting, file I/O, networking) with near-zero overhead.\n\n## Sandbox mounting\n\nWhen a workload needs a full Linux OS, agents can escalate to a full sandbox on demand without changing code. The [sandbox mounting](/docs/sandbox) extension mounts the sandbox as a filesystem and lets you execute commands on it, like mounting a hard drive on your own machine. Files written in the VM are available in the sandbox and vice versa.\n\nSee [agentOS vs Sandbox](/docs/versus-sandbox) for a detailed comparison.\n\n## Limitations\n\n### Software registry\n\nagentOS uses its own [software registry](/registry) of popular tools cross-compiled for the runtime. You cannot download and install arbitrary binaries (for example via `curl` or `apt`), and standard Linux package managers (`apt`, `yum`) are not available since agentOS runs a streamlined Linux environment rather than a full distribution. Native binaries that are not yet available in the registry (such as Go, Rust, or C++ toolchains) require a full [sandbox](/docs/sandbox).\n\nSee [Software](/docs/software) for how to install and configure available packages.\n\n### Lightweight Linux kernel\n\nagentOS provides a POSIX-compliant virtual Linux kernel with full filesystem operations, networking, and process management. It implements a focused subset of the kernel surface, so a few Linux-specific features are not available:\n\n- Kernel modules and eBPF\n- Container runtimes (e.g. Docker)\n- File watching (`inotify`, `fs.watch`)\n\n### No hardware access\n\nThe VM has no access to GPUs, USB devices, or other hardware.","src/content/docs/docs/limitations.mdx","9f65ad4a48fd07f0","docs/llm-credentials",{"id":138,"data":140,"body":143,"filePath":144,"digest":145,"deferredRender":16},{"title":141,"description":142,"skill":16},"LLM Credentials","Pass LLM API keys to agent sessions securely.","Pass LLM provider API keys to agent sessions so keys stay on the server and are injected at session creation, with per-tenant isolation for multi-tenant deployments.\n\n## Passing API keys\n\nPass LLM provider keys via the `env` option on `createSession`. The VM does not inherit from the host `process.env`, so keys must be passed explicitly.\n\n\u003CCodeSnippet file=\"examples/llm-credentials/client.ts\" />\n\n## Per-tenant credentials\n\nGive each tenant an isolated VM by keying `getOrCreate` on the tenant id, look up that tenant's API key on the server, and inject it via the session `env`. Credentials stay on the server and never reach the client.\n\nFirst, declare the agent software on the server:\n\n\u003CCodeSnippet file=\"examples/llm-credentials/server.ts\" />\n\nThen resolve each tenant's key and pass it at session creation:\n\n\u003CCodeSnippet file=\"examples/llm-credentials/per-tenant.ts\" />\n\nBecause keys are resolved per tenant from your own credential store (the `lookupTenantApiKey` stand-in above) and stay on the server, each session uses the tenant's own key and one tenant's key never reaches another tenant or the client.\n\n## Embedded LLM Gateway\n\nThe [Embedded LLM Gateway](/docs/llm-gateway) (coming soon) will remove the need to manage API keys manually. It routes all agent LLM requests through a managed proxy built into agentOS, providing per-tenant usage metering, rate limiting, and cost controls without deploying a separate gateway service.","src/content/docs/docs/llm-credentials.mdx","82c547d5f2ebc014","docs/llm-gateway",{"id":146,"data":148,"body":151,"filePath":152,"digest":153,"deferredRender":16},{"title":149,"description":150,"skill":16},"Embedded LLM Gateway","Route, meter, and manage LLM API calls from agents.","{/* TODO: This page is coming soon. */}\n\nThe Embedded LLM Gateway runs as part of the agentOS library, not as an external service. It intercepts and manages all LLM API calls made by agents inside the VM.\n\n- **Unified routing** for all agent LLM requests\n- **API keys stay on the server** so they are never exposed to agent code inside the VM\n- **Usage metering** with per-session and per-agent breakdowns\n- **Rate limiting** and cost controls\n\nCheck back soon for full documentation.","src/content/docs/docs/llm-gateway.mdx","cbe27275943ef64e","docs/multiplayer",{"id":154,"data":156,"body":159,"filePath":160,"digest":161,"deferredRender":16},{"title":157,"description":158,"skill":16},"Multiplayer","Connect multiple clients to the same agentOS actor for collaborative agent workflows.","Connect multiple clients to the same agentOS actor so all subscribers receive broadcasted session output, process logs, and shell data, enabling collaborative patterns where one user prompts and others observe.\n\n## Multiple clients observing a session\n\nAll clients connected to the same actor receive broadcasted events. This enables building collaborative UIs where multiple users watch an agent work.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/multiplayer/observe-session.ts\" />\n\n\u003CCodeSnippet file=\"examples/multiplayer/server.ts\" />\n\u003C/CodeGroup>","src/content/docs/docs/multiplayer.mdx","0f2695d49d1e4f9c","docs/networking",{"id":162,"data":164,"body":167,"filePath":168,"digest":169,"deferredRender":16},{"title":165,"description":166,"skill":16},"Networking & Previews","Proxy HTTP requests into agentOS VMs and create shareable preview URLs.","Proxy HTTP requests into VM services with `vmFetch` and create time-limited, token-based preview URLs (with configurable expiration, revocation, and CORS), all carried over one transport (the kernel socket table) that is loopback-only by default under a three-layer confinement model.\n\n## Run an HTTP server in the VM\n\nGuest code runs a normal Node HTTP server: it binds a loopback port inside the VM exactly like any Node process. Write the server file and spawn it.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/networking/client-run-server.ts\" />\n\u003CCodeSnippet file=\"examples/networking/server.ts\" />\n\u003C/CodeGroup>\n\n## Fetch from a VM service\n\nWith the HTTP server running in the VM (above), send requests to it with `vmFetch`, including custom methods, headers, and body.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/networking/client-fetch.ts\" />\n\u003CCodeSnippet file=\"examples/networking/client-fetch-options.ts\" />\n\u003CCodeSnippet file=\"examples/networking/server.ts\" />\n\u003C/CodeGroup>\n\n## Preview URLs\n\nPreview URLs are port forwarding for VM services: a time-limited, public URL that proxies HTTP to a port inside the VM, for browser or external access (use `vmFetch` for server-to-server). Tokens survive sleep/wake and CORS is enabled; see [Security](/docs/security-model) for details.\n\n### Create a preview URL\n\nToken lifetimes are configured under the `preview` key:\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/networking/server-preview.ts\" />\n\u003CCodeSnippet file=\"examples/networking/client-preview.ts\" />\n\u003C/CodeGroup>\n\n### Revoke a preview URL\n\nMint short-lived preview tokens so access expires automatically; the lifetime is capped by `preview.maxExpiresInSeconds`.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/networking/client-revoke.ts\" />\n\u003CCodeSnippet file=\"examples/networking/server.ts\" />\n\u003C/CodeGroup>\n\n## Permissions\n\nNetwork access is governed by the VM permission policy. By default the guest cannot reach the network; grant it, or allow only specific destinations:\n\n```ts\nconst vm = agentOS({\n permissions: {\n network: {\n default: \"deny\",\n rules: [{ mode: \"allow\", operations: [\"*\"], patterns: [\"api.example.com\"] }],\n },\n },\n});\n```\n\nSee [Permissions](/docs/permissions) for the full configuration.","src/content/docs/docs/networking.mdx","be5a561ec89aa62e","docs/nodejs-runtime",{"id":170,"data":172,"body":175,"filePath":176,"digest":177,"deferredRender":16},{"title":173,"description":174,"skill":16},"Node.js Runtime","Run Node.js in agentOS: native V8 acceleration, the node CLI, installing packages, and Node.js compatibility.","agentOS runs **Node.js** (`process.version` `v22.0.0`), fully isolated from the host. `node`, `npm`, and `npx` are on the `PATH`.\n\n## JavaScript Acceleration\n\nNormally, JavaScript running inside WebAssembly is exceptionally slow. In agentOS, JavaScript runs inside a native V8 isolate (powered by [Secure Exec](https://secureexec.dev)) for native runtime speeds:\n\n- **Native V8 speed, no overhead** — guest JS runs on V8's full JIT, not a WASM translation layer.\n- **Lower memory than a Node.js process** — each agent is a V8 isolate, not a full process, so many fit where process-per-agent fits a handful. See [benchmarks](https://secureexec.dev/docs/benchmarks).\n\n## Running Node\n\n```ts\nawait agent.exec('node -e \"console.log(1 + 1)\"'); // inline\nawait agent.exec(\"node /workspace/main.js a b\"); // script + argv\nawait agent.exec(\"npx tsx script.ts\"); // npx\nawait agent.exec('echo \"console.log(42)\" | node'); // stdin\n```\n\n`node` works directly (`exec` / `execArgv` / `spawn`), through the guest shell (`sh -c`, pipes), and as a REPL.\n\n## Installing packages\n\n### Ahead of time\n\nMount a host `node_modules` tree — projected read-only at `/root/node_modules` and resolved exactly like Node.js (ancestor walk, `package.json` `exports`/`imports`, symlinks — so pnpm/yarn layouts work), for both `import` and `require`:\n\n```ts\nimport { agentOS, setup, nodeModulesMount } from \"@rivet-dev/agentos\";\n\nconst vm = agentOS({\n mounts: [nodeModulesMount(\"/absolute/path/to/node_modules\")],\n});\n```\n\n### At runtime\n\nOr install in the VM mid-task:\n\n```ts\nawait agent.exec(\"npm install chalk\");\nawait agent.exec(\"node /workspace/app.js\"); // app.js: require(\"chalk\")\n```\n\n## Node.js compatibility\n\nGuest code runs as Node.js v22, isolated from the host. `node:` builtins — `fs`, `net`, `http`, `crypto`, undici-backed `fetch`, and more — are provided by the runtime, never the host's. See the full [Node.js Compatibility](https://secureexec.dev/docs/nodejs-compatibility) matrix.","src/content/docs/docs/nodejs-runtime.mdx","de98ead7ed62a81c","docs/permissions",{"id":178,"data":180,"body":183,"filePath":184,"digest":185,"deferredRender":16},{"title":181,"description":182,"skill":16},"Permissions","The per-scope kernel permission policy that gates every guest syscall in the sandbox.","The sandbox permission policy is the kernel-level enforcement layer. Every guest syscall the agent's sandboxed code makes is checked against a per-scope policy before any host resource is touched.\n\n- **Six scopes**, configured independently: `fs`, `network`, `childProcess`, `process`, `env`, `binding`.\n- **Each scope** is a mode (`\"allow\"` or `\"deny\"`), or a rule set.\n- **A denied operation** is rejected with `EACCES` before any host resource is touched.\n- **Merged over a secure default**, so partial policies work.\n\nFor the higher-level agent tool-approval layer (human-in-the-loop, auto-approve), see [Approvals](/docs/approvals).\n\n## Defaults and merge semantics\n\nThe sandbox is deny-by-default for outward-facing capabilities. When you pass no policy, this baseline applies:\n\n```ts\n{\n fs: \"allow\", // virtualized in-memory filesystem only\n childProcess: \"allow\",\n process: \"allow\",\n env: \"allow\",\n network: \"deny\", // no network egress until you opt in\n}\n```\n\n- `fs`/`childProcess`/`process`/`env` are allowed because they are fully virtualized (the guest sees only the VM, never the host) and are required to run a program at all.\n- `network` is denied: guest code cannot reach the network until you opt in.\n- Your policy is merged **over** this baseline. Omitted scopes keep their default; they are **not** denied. So `{ network: \"allow\" }` grants the network while keeping the execution essentials.\n\n\u003CCodeSnippet file=\"examples/permissions/server.ts\" region=\"grant-network\" />\n\n## Permission scopes\n\n| Scope | Controls | Default |\n|---|---|---|\n| `fs` | Filesystem reads, writes, and metadata operations | `allow` |\n| `network` | Outbound connections: `fetch`, HTTP, DNS, and inbound `listen` | `deny` |\n| `childProcess` | Spawning child processes | `allow` |\n| `process` | Process-control operations | `allow` |\n| `env` | Environment variable access | `allow` |\n| `binding` | Invoking bindings registered with the runtime | `deny`* |\n\n\\* The `binding` scope is auto-granted to `allow` when you register bindings and set no `binding` policy of your own. Pass a `binding` policy to gate individual bindings.\n\n## Bind a policy to the VM\n\nA policy is a plain object keyed by scope. Pass it as `permissions` to `agentOS(...)` and it gates every guest syscall on that VM.\n\n\u003CCodeSnippet file=\"examples/permissions/bind-policy.ts\" />\n\n## Grant or deny a whole scope\n\nThe simplest value for a scope is a single mode string. `\"allow\"` permits every operation in the scope; `\"deny\"` rejects every one with `EACCES`. Omitted scopes keep their secure default, so you only list what you want to change.\n\n```ts\nconst permissions = {\n\tnetwork: \"allow\", // turn on network egress\n\tfs: \"deny\", // turn off all filesystem access\n};\n```\n\nThere is no typed `\"ask\"` mode. Interactive, human-in-the-loop approval lives in the higher-level [Approvals](/docs/approvals) layer, not the kernel policy. To block at the kernel level, use `\"deny\"`.\n\n## Allow only specific filesystem paths\n\nFor finer control, a scope can be a rule set instead of a bare mode: a `default` mode plus an ordered list of `rules`. The `fs` scope matches by `paths` (filesystem globs). Each rule names its `operations` (`read`, `write`, `stat`, `readdir`, `create_dir`, `rm`, `rename`, `symlink`, `readlink`, `chmod`, `truncate`, `mount_sensitive`, or `[\"*\"]` for all). Last matching rule wins; if no rule matches, `default` applies.\n\n\u003CCodeSnippet file=\"examples/permissions/fs-rules.ts\" region=\"deny-vault\" />\n\nTo invert it, flip `default` to `\"deny\"` and allow just one subtree:\n\n\u003CCodeSnippet file=\"examples/permissions/fs-rules.ts\" region=\"allow-only-data\" />\n\n## Allow only specific network hosts\n\nEvery non-`fs` scope matches by `patterns` instead of `paths`. For `network`, a pattern is a host (or `host:port`), and the operations are `fetch`, `http`, `dns`, and `listen`.\n\n\u003CCodeSnippet file=\"examples/permissions/server.ts\" region=\"allow-one-host\" />\n\n## Allow only specific bindings\n\nBindings registered with the runtime are gated by the `binding` scope, matched by name via `patterns`. Bindings have no sub-operations, so pass `[\"*\"]` for `operations`.\n\n\u003CCodeSnippet file=\"examples/permissions/server.ts\" region=\"allow-one-binding\" />\n\nThe `childProcess`, `process`, and `env` scopes work the same way: `childProcess` patterns match the command (`operations: [\"spawn\"]`), `env` patterns match the variable name (`operations: [\"read\", \"write\"]`), and `process` is matched by pattern with `operations: [\"*\"]`.\n\n## Combine policies and see denials\n\nEach policy above sets one scope, so you can spread several into one `permissions` object and bind them together.\n\n\u003CCodeSnippet file=\"examples/permissions/combine.ts\" />\n\nWhen a scope or matching rule denies an operation, the kernel rejects it with `EACCES` before any host resource is touched. For example, with `network: \"deny\"`, an outbound `fetch()` inside the guest throws:\n\n```\nEACCES: permission denied, tcp://example.com:80: blocked by network.http policy\n```","src/content/docs/docs/permissions.mdx","a5a4f270ee95e1ad","docs/persistence",{"id":186,"data":188,"body":191,"filePath":192,"digest":193,"deferredRender":16},{"title":189,"description":190,"skill":16},"Persistence & Sleep","How agentOS persists data and manages sleep/wake cycles.","agentOS automatically persists the `/home/agentos` filesystem and session transcripts (with sequence numbers for replay) across sleep/wake, sleeping after a configurable grace period (15 minutes by default) and waking automatically when a client connects or a cron job triggers.\n\n## What persists across sleep\n\n| Data | Storage | Persists? |\n|------|---------|-----------|\n| Files in `/home/agentos` | Persistent filesystem | Yes |\n| Session records | SQLite (`agent_os_sessions`) | Yes |\n| Session event history | SQLite (`agent_os_session_events`) | Yes |\n| Preview URL tokens | SQLite (`agent_os_preview_tokens`) | Yes |\n| Cron job definitions | Actor state | Yes |\n| Running processes | VM kernel | No |\n| Active shells | VM kernel | No |\n| In-memory mounts | VM memory | No |\n| VM kernel state | VM memory | No |\n\n## What prevents sleep\n\nThe actor stays awake as long as any of these are active:\n\n- **Active sessions** (created but not closed/destroyed)\n- **Running processes** (spawned but not exited)\n- **Active shells** (opened but not closed)\n- **Pending hooks** (server-side callbacks still executing)\n\nWhen all activity stops, the sleep grace period begins.\n\n## Sleep grace period\n\nAfter all activity stops, the actor waits 15 minutes before sleeping. This allows for brief pauses between interactions without restarting the VM.\n\n```\nActivity stops ──> 15 min grace period ──> Actor sleeps\n (VM shutdown, processes killed)\n\nNew client connects ──> Actor wakes ──> VM boots ──> Filesystem restored\n```\n\n## Timeouts\n\n| Setting | Default | Description |\n|---------|---------|-------------|\n| Action timeout | 15 minutes | Maximum time for any single action |\n| Sleep grace period | 15 minutes | Time before sleeping after all activity stops |\n\nThese are set internally by the `agentOS()` factory and cannot be overridden per-call.\n\n## Sleep vs destroy\n\n| | Sleep | Destroy |\n|-|-------|---------|\n| Filesystem | Preserved | Deleted |\n| Session records | Preserved | Deleted |\n| Event history | Preserved | Deleted |\n| Preview tokens | Preserved | Deleted |\n| VM state | Lost | Lost |\n| Processes | Killed | Killed |\n\n## VM boot and shutdown events\n\nSubscribe to `vmBooted` and `vmShutdown` events to track VM lifecycle.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/persistence/lifecycle-client.ts\" />\n\n\u003CCodeSnippet file=\"examples/persistence/server.ts\" />\n\u003C/CodeGroup>\n\n## Resuming after sleep\n\nWhen the actor wakes up, the VM boots and the filesystem is restored from SQLite, session records and event history are immediately available, and processes and shells from the previous session are gone. Clients can reconnect, list prior work with `listPersistedSessions` (which works without a running VM), and replay a session's persisted transcript with `getSessionEvents`.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/persistence/resume-client.ts\" />\n\u003C/CodeGroup>\n\n\n## Persisted tables schema\n\n### `agent_os_fs_entries`\n\nStores the virtual filesystem.\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `path` | TEXT PRIMARY KEY | File or directory path |\n| `is_directory` | INTEGER | 1 for directory, 0 for file |\n| `content` | BLOB | File content |\n| `mode` | INTEGER | POSIX mode bits |\n| `size` | INTEGER | File size in bytes |\n| `atime_ms` | INTEGER | Access time (ms) |\n| `mtime_ms` | INTEGER | Modification time (ms) |\n| `ctime_ms` | INTEGER | Change time (ms) |\n| `birthtime_ms` | INTEGER | Birth time (ms) |\n\n### `agent_os_sessions`\n\nStores session metadata.\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `session_id` | TEXT PRIMARY KEY | Unique session identifier |\n| `agent_type` | TEXT | Agent type (e.g. \"pi\") |\n| `capabilities` | TEXT (JSON) | Agent capabilities |\n| `agent_info` | TEXT (JSON) | Agent metadata |\n| `created_at` | INTEGER | Creation timestamp (ms) |\n\n### `agent_os_session_events`\n\nStores session event history.\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `id` | INTEGER PRIMARY KEY | Auto-incrementing ID |\n| `session_id` | TEXT | Session reference |\n| `seq` | INTEGER | Sequence number within session |\n| `event` | TEXT (JSON) | JSON-RPC notification |\n| `created_at` | INTEGER | Timestamp (ms) |","src/content/docs/docs/persistence.mdx","2ae78aaf60a662a8","docs/processes",{"id":194,"data":196,"body":199,"filePath":200,"digest":201,"deferredRender":16},{"title":197,"description":198,"skill":16},"Processes & Shell","Execute commands, spawn long-running processes, and open interactive shells in agentOS VMs.","Run commands with one-shot `exec`, spawn long-running processes with streaming stdout/stderr and stdin, manage their lifecycle (stop, kill, wait, inspect), open interactive PTY-backed shells, and inspect the process tree across all VM runtimes.\n\n## One-shot execution\n\nUse `exec` to run a command and wait for completion. Returns stdout, stderr, and exit code.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/processes/exec.ts\" />\n\u003CCodeSnippet file=\"examples/processes/server.ts\" />\n\u003C/CodeGroup>\n\n## Spawn a long-running process\n\nUse `spawn` for processes that run in the background. Output is streamed via `processOutput` and `processExit` events.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/processes/spawn.ts\" />\n\u003CCodeSnippet file=\"examples/processes/server.ts\" />\n\u003C/CodeGroup>\n\n## Write to stdin\n\nSend input to a running process.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/processes/stdin.ts\" />\n\u003CCodeSnippet file=\"examples/processes/server.ts\" />\n\u003C/CodeGroup>\n\n## Process lifecycle\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/processes/lifecycle.ts\" />\n\u003CCodeSnippet file=\"examples/processes/server.ts\" />\n\u003C/CodeGroup>\n\n## Interactive shells\n\nOpen an interactive shell with PTY support. Shell data is streamed via `shellData` events.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/processes/shell.ts\" />\n\u003CCodeSnippet file=\"examples/processes/server.ts\" />\n\u003C/CodeGroup>","src/content/docs/docs/processes.mdx","bea0f21a7222580c","docs/python-runtime",{"id":202,"data":204,"body":207,"filePath":208,"digest":209,"deferredRender":16},{"title":205,"description":206,"skill":16},"Python Runtime","Run Python in agentOS: the python CLI, installing packages ahead of time or at runtime, and what's supported.","agentOS runs **CPython 3.13** as a first-class runtime. `python` and `python3` are on the `PATH` and plug into the VM's filesystem, processes, and network — agents use them like any other command.\n\n## Running Python\n\n```ts\nawait agent.exec('python -c \"print(1 + 1)\"'); // inline\nawait agent.exec(\"python /workspace/main.py a b\"); // script + sys.argv\nawait agent.exec(\"python -m http.server 8000\"); // module\nawait agent.exec('echo \"print(40 + 2)\" | python -'); // stdin\n```\n\n`python` works directly (`exec` / `execArgv` / `spawn`), through the guest shell (`sh -c`, pipes), and as an interactive REPL.\n\n## Installing packages\n\n`pip install` writes to a persistent spot on the VM filesystem, so a package installed once is importable by every later `python` run in that VM.\n\n### Ahead of time\n\nInstall once during setup so the agent starts ready — no install cost mid-task:\n\n```ts\n// one-off setup pass on the VM, before handing it to the agent\nawait agent.exec(\"pip install requests pandas\");\n// requests + pandas now import in every python run on this VM\n```\n\n### At runtime\n\nOr let the agent install what it needs, mid-task:\n\n```ts\nawait agent.exec(\"pip install rich\");\nawait agent.exec('python -c \"import rich; print(rich.__version__)\"');\n```\n\n`pip install \u003Cpkg>` and `python -m pip install \u003Cpkg>` both work; downloads obey the VM's network policy (default-deny + allowlist).\n\n## Compatibility\n\nCPython 3.13 and the standard library, with a few VM-shaped differences.\n\n### Supported\n\n- Full VM filesystem (`/tmp`, `/etc`, `/root`, …) — shared with other processes and `readFile()`\n- Reading, writing, creating, deleting, and renaming files anywhere on the VM, plus symlinks and file metadata (`os.symlink` / `os.readlink` / `os.chmod` / `os.chown` / `os.utime`)\n- A real process in the tree: stdin/stdout/stderr, signals, `kill`\n- `subprocess` launching other VM commands (`node`, etc.)\n- Pure-Python packages, plus native packages with a prebuilt wheel — `numpy`, `pandas`, `scipy`, `scikit-learn`, `pydantic`, `cryptography`, `Pillow`, and [many more](https://pyodide.org/en/stable/usage/packages-in-pyodide.html)\n- `requests`, `urllib`, and `pip` over HTTP/DNS, under the VM network policy\n- Outbound raw TCP and UDP sockets (the `socket` module), under the VM network policy\n\n### Unsupported\n\n- OS threads and `multiprocessing` — the runtime is single-threaded\n- `os.fork` / `os.exec`\n- Some packages with native (C/Rust) extensions — see the [full list of supported packages](https://pyodide.org/en/stable/usage/packages-in-pyodide.html)\n- Socket servers / listeners (`bind`/`listen`/`accept`) — outbound connections only","src/content/docs/docs/python-runtime.mdx","46e34a931b33b0bc","docs/quickstart",{"id":210,"data":212,"body":215,"filePath":216,"digest":217,"deferredRender":16},{"title":213,"description":214,"skill":16},"Quickstart","Set up an agentOS actor, create a session, and run your first coding agent.","import DeployTargets from '../../../components/DeployTargets.astro';\nimport { AGENT_PROMPT } from '../../../components/marketing/agentPrompt';\n\n\u003Cdiv style=\"border-radius:0.75rem;border:1px solid rgba(27,25,22,0.12);background:rgba(27,25,22,0.035);padding:0.875rem 1.125rem;margin:1.5rem 0;color:#56524a;display:flex;align-items:center;justify-content:space-between;gap:1.25rem;\">\n\u003Cspan>Use this pre-built prompt to get started faster.\u003C/span>\n\u003Cbutton type=\"button\" onclick=\"var b=this;navigator.clipboard.writeText(b.getAttribute('data-prompt')||'').then(function(){b.textContent='Copied!';setTimeout(function(){b.textContent='Copy prompt';},1500);});\" data-prompt={AGENT_PROMPT} style=\"appearance:none;border:1px solid rgba(27,25,22,0.18);background:#1b1916;color:#f4f1e7;font-family:var(--sl-font);font-size:0.8rem;font-weight:600;display:inline-flex;align-items:center;justify-content:center;height:2rem;padding:0 0.85rem;border-radius:6px;cursor:pointer;white-space:nowrap;margin-top:0;flex:none;box-sizing:border-box;\">Copy prompt\u003C/button>\n\u003C/div>\n\n\u003Cdiv style=\"border-radius:0.75rem;border:1px solid rgba(27,25,22,0.12);background:rgba(27,25,22,0.035);padding:0.875rem 1.125rem;margin:1.5rem 0;color:#56524a;display:flex;align-items:center;justify-content:space-between;gap:1.25rem;\">\n\u003Cspan>Prefer to read code? Clone the example repository.\u003C/span>\n\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/quickstart-app\" style=\"appearance:none;border:1px solid rgba(27,25,22,0.18);background:transparent;color:#1b1916;font-family:var(--sl-font);font-size:0.8rem;font-weight:600;display:inline-flex;align-items:center;justify-content:center;height:2rem;padding:0 0.85rem;border-radius:6px;cursor:pointer;white-space:nowrap;text-decoration:none;flex:none;gap:0.45rem;box-sizing:border-box;\">\u003Csvg viewBox=\"0 0 496 512\" width=\"14\" height=\"14\" fill=\"currentColor\" aria-hidden=\"true\">\u003Cpath d=\"M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z\"/>\u003C/svg>View on GitHub\u003C/a>\n\u003C/div>\n\n\u003Csvg viewBox=\"0 0 400 210\" role=\"img\" aria-label=\"A client (JavaScript, browser, or another backend) connects to a server that runs each agent in its own isolated VM, marked with the agentOS 'OS' logo.\" style=\"width:100%;height:auto;max-width:420px;display:block;margin:3rem auto 2.5rem;\">\n \u003Cdefs>\n \u003Cmarker id=\"qs-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003Csymbol id=\"qs-os\" viewBox=\"0 0 100 100\">\n \u003Crect x=\"8\" y=\"8\" width=\"84\" height=\"84\" rx=\"26\" fill=\"none\" stroke=\"#1b1916\" stroke-width=\"8\" />\n \u003Ctext x=\"50\" y=\"50\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"var(--sl-font)\" font-weight=\"700\" font-size=\"38\" fill=\"#1b1916\">OS\u003C/text>\n \u003C/symbol>\n \u003C/defs>\n \u003Crect x=\"12\" y=\"67\" width=\"140\" height=\"60\" rx=\"12\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"82\" y=\"92\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"15\" font-weight=\"600\" fill=\"#1b1916\">Client\u003C/text>\n \u003Ctext x=\"82\" y=\"112\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#56524a\">JS · Browser · Backend\u003C/text>\n \u003Cline x1=\"154\" y1=\"97\" x2=\"205\" y2=\"97\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#qs-arrow)\" />\n \u003Crect x=\"210\" y=\"40\" width=\"164\" height=\"114\" rx=\"14\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"224\" y=\"62\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">Server\u003C/text>\n \u003Cg fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.2\">\n \u003Crect x=\"224\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"260\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"296\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"332\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"224\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"260\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"296\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"332\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003C/g>\n \u003Cg>\n \u003Cuse href=\"#qs-os\" x=\"229\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"265\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"301\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"337\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"229\" y=\"117\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"265\" y=\"117\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"301\" y=\"117\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"337\" y=\"117\" width=\"18\" height=\"18\" />\n \u003C/g>\n \u003Cg>\n \u003Crect x=\"146\" y=\"170\" width=\"15\" height=\"15\" rx=\"4\" fill=\"none\" stroke=\"#56524a\" stroke-width=\"1.4\" />\n \u003Ctext x=\"153.5\" y=\"178\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"var(--sl-font)\" font-weight=\"700\" font-size=\"7\" fill=\"#56524a\">OS\u003C/text>\n \u003Ctext x=\"170\" y=\"178\" dominant-baseline=\"central\" font-family=\"var(--sl-font)\" font-size=\"12\" fill=\"#56524a\">= agentOS VM\u003C/text>\n \u003C/g>\n\u003C/svg>\n\n\u003CSteps>\n\n1. **Install**\n\n - **@rivet-dev/agentos** — Actor framework with built-in persistence and orchestration\n - **@agentos-software/pi** — [Pi](https://github.com/mariozechner/pi-coding-agent) coding agent (Claude Code, Codex, and OpenCode coming soon)\n\n ```bash\n npm install @rivet-dev/agentos @agentos-software/pi\n ```\n\n2. **Create the server**\n\n \u003CCodeSnippet file=\"examples/quickstart-app/server.ts\" />\n\n3. **Create the client**\n\n The client can be any public frontend or another backend. The same `vm` actor is reachable from a plain Node script, a browser/React app, or a separate server.\n\n \u003CCodeGroup>\n \u003CCodeSnippet file=\"examples/quickstart-app/client.ts\" />\n\n \u003CCodeSnippet file=\"examples/quickstart-app/Agent.tsx\" />\n \u003C/CodeGroup>\n\n4. **Run it**\n\n Start the server, then run the client in a second terminal:\n\n ```bash\n # Terminal 1: start the server\n npx tsx server.ts\n\n # Terminal 2: run the client\n npx tsx client.ts\n ```\n\n5. **Customize**\n\n Now that you have a working agent, customize it to fit your needs:\n\n - **[Software](/docs/software)** — Install software packages inside the VM\n - **[Filesystem](/docs/filesystem)** — Read, write, and manage files inside the VM\n - **[Permissions & Resource Limits](/docs/permissions)** — Gate what the agent can do and cap its resource usage\n - **[Bindings](/docs/bindings)** — Expose your JavaScript functions to agents as CLI commands\n\n5. **Deploy**\n\n By default, agentOS runs locally with `npx rivetkit dev` — no infrastructure needed. To run in production, deploy to any of these targets:\n\n \u003CDeployTargets />\n\n See [Deployment](/docs/deployment) for managed, self-hosted, and agentOS Core options.\n\n\u003C/Steps>\n\n\u003CNote>\nagentOS is in preview and the API is subject to change. If you run into issues, please [report them on GitHub](https://github.com/rivet-dev/rivet/issues) or [join our Discord](https://rivet.dev/discord).\n\u003C/Note>\n\n## agentOS Core\n\nThe quickstart above uses `@rivet-dev/agentos`, which includes statefulness, multiplayer, and orchestration out of the box. If you only need direct VM control without those features, you can use the core package (`@rivet-dev/agentos-core`) standalone.\n\nSee [agentOS core documentation](/docs/core) for reference.","src/content/docs/docs/quickstart.mdx","9cf346c3778a70e8","docs/resource-limits",{"id":218,"data":220,"body":223,"filePath":224,"digest":225,"deferredRender":16},{"title":221,"description":222,"skill":16},"Resource Limits","Cap per-VM processes, file descriptors, sockets, and filesystem bytes so guest code can never exhaust the host.","Every agentOS VM runs with **per-VM resource caps**. Runaway or malicious guest code can exhaust its own VM, but it can never starve the host or any sibling VM.\n\n- **Bounded by default**: each VM ships with conservative caps. Unset fields fall back to built-in defaults that match the runtime's historical constants.\n- **Per-VM**: every VM gets its own budget. Limits are not shared across VMs.\n- **Enforced by the kernel**: a guest that exceeds a cap fails inside the VM (out-of-memory, `EMFILE`, `EAGAIN`, etc.). The host is never affected.\n- **Operator-raisable**: the operator (the trusted process that creates the VM) may raise any cap for trusted workloads. Guest code can never raise its own caps.\n\n## Setting limits\n\nSet caps on the `limits` object in the `agentOS` config. Limits are grouped by subsystem (`resources` and more). Omitted limits keep their secure default.\n\n\u003CCodeSnippet file=\"examples/resource-limits/server.ts\" />\n\n## Available caps\n\n| Limit | Controls | Notes |\n|---|---|---|\n| `resources.maxProcesses` | Concurrent processes in the VM process table | Caps fork bombs and runaway spawning. New spawns fail with `EAGAIN`. |\n| `resources.maxOpenFds` | Open file descriptors | Exhausting the table fails with `EMFILE` / `ENFILE`. |\n| `resources.maxSockets` | Open sockets in the socket table | Bounds concurrent connections; excess `connect`/`accept` fail. |\n| `resources.maxFilesystemBytes` | Total bytes stored in the virtual filesystem | Bounds VFS storage; writes past the budget fail with a no-space error. |\n| `resources.maxWasmStackBytes` | Maximum WASM call-stack size, in bytes | Deep recursion fails with a stack overflow instead of crashing the VM. |\n\n## Behavior at the limit\n\n- **WASM stack**: deep recursion throws a stack-overflow error in the guest, never a host crash.\n- **Filesystem bytes**: writing past the VFS budget fails with a no-space error to the guest.\n- **Counts (fds / processes / sockets)**: hitting a table cap returns the standard POSIX errno (`EMFILE`, `EAGAIN`, etc.), exactly as a real Linux kernel would under `ulimit`.\n\n## Warnings & observability\n\nLimits are observable, not just enforced. Every bound — resource caps and the\ninternal bounded queues alike — is tracked in a central limit registry that:\n\n- **Warns before the limit is hit.** As usage crosses ~80% of a cap, the runtime\n emits a structured warning (once per crossing, re-armed only after it recovers),\n so a slow consumer or a runaway guest is visible *before* it fails.\n- **Applies backpressure instead of failing catastrophically.** The internal\n queues between the guest, the runtime, and the host block their producer when\n full rather than dropping data or tearing down the session — so a transient\n burst degrades to \"slower\", not \"broken\".\n- **Surfaces through logs.** secure-exec logs to stderr (stdout is the wire\n protocol); set `SECURE_EXEC_LOG=warn` (the default) to see near-limit warnings\n or `SECURE_EXEC_LOG=debug` for live per-limit usage snapshots.\n\nSee [Limits & Observability](/docs/architecture/limits-and-observability) for the\nfull architecture.","src/content/docs/docs/resource-limits.mdx","14df899437cdbdba","docs/sandbox",{"id":226,"data":228,"body":231,"filePath":232,"digest":233,"deferredRender":16},{"title":229,"description":230,"skill":16},"Sandbox Mounting","Extend agentOS with full sandboxes for heavy workloads like browsers, desktop automation, and compilation.","For heavy workloads like browsers, desktop automation, and compilation, pair agentOS with a full sandbox on demand. Its filesystem mounts into the VM as a native directory, and its process management is exposed as [bindings](/docs/bindings), all provider-agnostic through [Sandbox Agent](https://sandboxagent.dev).\n\n## Why use agentOS with a sandbox?\n\nagentOS is an alternative to sandboxes that covers most use cases, but some workloads need a full sandbox for special kinds of software (browsers, desktop automation, heavy compilation). Sandbox mounting lets you lazily start a sandbox on demand, only when it is needed, and project it into the VM. The hybrid model means one agent session can handle both lightweight coding tasks and heavy system operations, using the right tool for each.\n\nSee [agentOS vs Sandbox](/docs/versus-sandbox) for a detailed comparison.\n\n## When to use a sandbox\n\n- **Native binaries** not yet supported in the agentOS runtime.\n- **Browsers and desktop automation**: Playwright, Puppeteer, Selenium, or anything that needs a display server.\n- **Heavy compilation**: Large builds or native toolchains that require a full Linux environment.\n- **GUI applications**: Desktop apps, VNC sessions, or any workload that needs a graphical environment.\n- **Node.js packages with native extensions** (e.g. `sharp`, `bcrypt`, `better-sqlite3`) that require a full build toolchain.\n\nStart with the default agentOS VM for all workloads, and only spin up a sandbox when a task genuinely requires one. Sandboxes are billed per second of uptime, so start them on demand and tear them down when the task is done.\n\n## Getting started\n\nThe sandbox integration ships as the `@rivet-dev/agentos-sandbox` package. It works through two mechanisms:\n\n- **Filesystem mount**: Projects the sandbox into the VM as a native directory, like mounting a hard drive on your own machine. Read and write files through the mount directly.\n- **Bindings**: Exposes sandbox process management as [bindings](/docs/bindings). Execute commands on the sandbox from within the VM.\n\nBoth are powered by [Sandbox Agent](https://sandboxagent.dev), and you can swap providers without changing agent code. Install both packages:\n\n```bash\nnpm install @rivet-dev/agentos-sandbox sandbox-agent\n```\n\n`createSandboxFs` and `createSandboxBindings` come from `@rivet-dev/agentos-sandbox`. `SandboxAgent` and the provider helpers (such as `docker`) come from the `sandbox-agent` package.\n\n\u003CCodeSnippet file=\"examples/sandbox/server.ts\" />\n\n## Calling the mounted bindings\n\nOnce the sandbox is mounted, write code through the filesystem and run it inside the sandbox. The sandbox bindings are exposed inside the VM as a CLI command, so you call it through the same `exec`/`spawn` surface as any other command.\n\n\u003CCodeSnippet file=\"examples/sandbox/client.ts\" />\n\n## Bindings reference\n\nThe bindings expose these commands inside the VM:\n\n```bash\n# Run a command synchronously\nagentos-sandbox run-command --command \"npm install\" --cwd \"/app\"\n\n# Start a background process\nagentos-sandbox create-process --command \"npm\" --args \"run\" --args \"dev\"\n\n# List running processes\nagentos-sandbox list-processes\n\n# Get process output\nagentos-sandbox get-process-logs --id \"proc_abc123\"\n\n# Stop or kill a process\nagentos-sandbox stop-process --id \"proc_abc123\"\nagentos-sandbox kill-process --id \"proc_abc123\"\n\n# Send input to an interactive process\nagentos-sandbox send-input --id \"proc_abc123\" --data \"yes\"\n```\n\n## Sandbox providers\n\nThe extension works with any [Sandbox Agent](https://sandboxagent.dev) provider. See the [Sandbox Agent documentation](https://sandboxagent.dev) for available providers and setup instructions.","src/content/docs/docs/sandbox.mdx","cd14b3cdc9717d6b","docs/security-model",{"id":234,"data":236,"body":239,"filePath":240,"digest":241,"deferredRender":16},{"title":237,"description":238,"skill":16},"Security Model","Trust boundaries, isolation guarantees, and the agentOS threat model.","\u003CWarning>\nagentOS is in beta and still undergoing security review. The security model described here is subject to change.\n\u003C/Warning>\n\nagentOS is a sandbox: it runs **untrusted code safely on behalf of a trusted caller**. Every actor boots its own fully virtualized VM with a virtual filesystem, process table, socket table, pipes, PTYs, a permission policy, and managed language runtimes. Guest JavaScript executes in a V8 isolate, and every guest syscall is serviced by the kernel rather than the host. There are no host escapes: guest code cannot spawn a real host process, touch the real host filesystem, or open a real host network socket.\n\n## Deny by default\n\nNo syscalls are bound to the system by default. Everything is denied until explicitly opted in.\n\n- **Network access** is denied until you opt in with a `network` permission.\n- **Filesystem mounts** expose nothing of the host until you configure them.\n- **Process spawning** runs only kernel-managed guest processes, never host processes.\n- **All other host capabilities** must be configured by the host before the VM can use them.\n\nOther in-VM scopes (the virtual filesystem, child processes, process info, env) are enabled so that normal programs run, but they are mediated entirely by the kernel and never touch the host.\n\n## Trust model: three components\n\nBefore judging whether something is a security bug, decide which side of the boundary it is on. agentOS has three components with very different trust levels.\n\n\u003Csvg width=\"700\" height=\"270\" viewBox=\"0 0 700 270\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"Three-component trust model: client, sidecar, executor\">\n \u003Crect x=\"20\" y=\"40\" width=\"180\" height=\"190\" rx=\"10\" fill=\"#f4f6f8\" stroke=\"#c8d0d8\" stroke-width=\"1.5\" />\n \u003Ctext x=\"110\" y=\"68\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#111827\">Client\u003C/text>\n \u003Ctext x=\"110\" y=\"90\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"12\" fill=\"#374151\">(trusted)\u003C/text>\n \u003Ctext x=\"110\" y=\"120\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">Your host app\u003C/text>\n \u003Ctext x=\"110\" y=\"138\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">Configures the VM\u003C/text>\n \u003Ctext x=\"110\" y=\"170\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#9a3412\">Untrusted: only the\u003C/text>\n \u003Ctext x=\"110\" y=\"186\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#9a3412\">code it submits\u003C/text>\n \u003Crect x=\"260\" y=\"40\" width=\"180\" height=\"190\" rx=\"10\" fill=\"#eef2f6\" stroke=\"#c8d0d8\" stroke-width=\"1.5\" />\n \u003Ctext x=\"350\" y=\"68\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#111827\">Sidecar / Kernel\u003C/text>\n \u003Ctext x=\"350\" y=\"90\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"12\" fill=\"#374151\">(trusted = TCB)\u003C/text>\n \u003Ctext x=\"350\" y=\"120\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">Owns VFS, processes,\u003C/text>\n \u003Ctext x=\"350\" y=\"138\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">sockets, policy\u003C/text>\n \u003Ctext x=\"350\" y=\"170\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">Enforces the\u003C/text>\n \u003Ctext x=\"350\" y=\"186\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">boundary\u003C/text>\n \u003Crect x=\"500\" y=\"40\" width=\"180\" height=\"190\" rx=\"10\" fill=\"#fdf2f2\" stroke=\"#e6b8b8\" stroke-width=\"1.5\" />\n \u003Ctext x=\"590\" y=\"68\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#111827\">Executor\u003C/text>\n \u003Ctext x=\"590\" y=\"90\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"12\" fill=\"#9a3412\">(untrusted = adversary)\u003C/text>\n \u003Ctext x=\"590\" y=\"120\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">V8 isolate / WASM\u003C/text>\n \u003Ctext x=\"590\" y=\"138\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">Runs guest code\u003C/text>\n \u003Ctext x=\"590\" y=\"170\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#9a3412\">Assume actively\u003C/text>\n \u003Ctext x=\"590\" y=\"186\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#9a3412\">hostile\u003C/text>\n \u003Cline x1=\"200\" y1=\"135\" x2=\"258\" y2=\"135\" stroke=\"#6b7280\" stroke-width=\"1.5\" marker-end=\"url(#arrow)\" />\n \u003Cline x1=\"440\" y1=\"135\" x2=\"498\" y2=\"135\" stroke=\"#b91c1c\" stroke-width=\"1.5\" stroke-dasharray=\"4 3\" marker-end=\"url(#arrowred)\" />\n \u003Ctext x=\"229\" y=\"128\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"9\" fill=\"#6b7280\">wire\u003C/text>\n \u003Ctext x=\"469\" y=\"128\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"9\" fill=\"#b91c1c\">syscalls\u003C/text>\n \u003Ctext x=\"469\" y=\"252\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" font-weight=\"700\" fill=\"#b91c1c\">SECURITY BOUNDARY\u003C/text>\n \u003Cdefs>\n \u003Cmarker id=\"arrow\" markerWidth=\"8\" markerHeight=\"8\" refX=\"6\" refY=\"3\" orient=\"auto\">\u003Cpath d=\"M0,0 L6,3 L0,6 Z\" fill=\"#6b7280\" />\u003C/marker>\n \u003Cmarker id=\"arrowred\" markerWidth=\"8\" markerHeight=\"8\" refX=\"6\" refY=\"3\" orient=\"auto\">\u003Cpath d=\"M0,0 L6,3 L0,6 Z\" fill=\"#b91c1c\" />\u003C/marker>\n \u003C/defs>\n\u003C/svg>\n\n### Client (trusted)\n\nThe party that configures and manages the VM: your application code, container, or serverless function.\n\n- The client process and **every value it sends** are trusted: VM config, mount descriptors and their plugin configs (host directory paths, S3 endpoints and credentials, etc.), the permission policy, network allowlist, resource limits, env, and DNS overrides.\n- **Configuration is not an attack surface.** A defect that requires the client to supply a malicious config, endpoint, credential, or policy is not a sandbox vulnerability: the client is configuring its own VM and already controls the host.\n- The **one** thing from the client that is *not* trusted is the **code/payload** it asks to run, because that runs in the executor. How code reached the executor never makes it trusted.\n\nYou are responsible for hardening this side. See [What you are responsible for](#what-you-are-responsible-for).\n\n### Sidecar / kernel (trusted, the enforcement point)\n\nThe trusted computing base. It brokers client requests and owns the kernel, VFS, mount/plugin registry, socket table, and permission policy. It is responsible for enforcing the boundary against the executor.\n\n### Executor (untrusted, the adversary)\n\nV8 isolates or WASM running guest JS/Python/WASM plus any third-party, npm, or agent-generated code.\n\n- Assume everything here is **actively hostile**.\n- The executor reaches the outside world only through kernel-owned VFS, process, socket, pipe, PTY, permission, and DNS paths.\n\n## The security boundary\n\n**The security boundary is sidecar ↔ executor.** The runtime must stop guest code in the executor from:\n\n- Escaping the kernel boundary (the real host filesystem, network, process table, or memory).\n- Bypassing the **applied** permission policy, allowlist, or limits.\n- Exhausting host resources beyond configured bounds.\n- Reading another VM's state.\n\nTwo corollaries that are easy to get wrong:\n\n- **Trusted policy, untrusted subject.** The permission policy and limits are trusted input, but the guest executor is the subject they bind. \"Guest bypasses an applied permission, egress rule, or resource cap\" is in-scope and serious. Trusted = who sets the rule; untrusted = who is bound by it.\n- **Trusted mount, untrusted traffic.** A host-backed mount (host directory, S3, etc.) comes from trusted config, so its existence, target, and credentials are not attack surface. But the guest drives I/O through it, so confining those guest operations to the mount root (symlink, `..`, TOCTOU, and path-aliasing escapes) is in-scope.\n\n### In scope vs out of scope\n\n| In scope (sandbox escape) | Out of scope (not a sandbox bug) |\n| --- | --- |\n| Guest reaches the real host fs / net / process / memory | Client supplies a malicious config / endpoint / credential / policy |\n| Guest bypasses an applied permission, egress rule, or limit | Hardening that only guards trusted client-provided configuration |\n| Guest exhausts host resources past configured bounds | Wire-level authn/authz between mutually distrusting clients |\n| Guest reads another VM's state | VM-to-VM access via forged connection IDs (single-client transport) |\n| Guest escapes a host-backed mount root (symlink / `..` / TOCTOU) | The existence or target of a configured mount |\n\n**Transport scope.** The wire protocol is same-version lockstep and single-client over stdio (one trusted client per sidecar process). There is no second, mutually-distrusting client, so wire-level authn/authz between clients and VM-to-VM access via forged connection IDs are out of scope until a multi-client transport exists.\n\n## VM isolation\n\nEach agentOS actor runs in its own isolated VM.\n\n- **Sandboxed execution.** All agent code runs inside a V8 isolate with WebAssembly. No code escapes the isolate boundary.\n- **Virtual filesystem.** The VM has its own in-memory filesystem. Guest reads and writes never reach the real host filesystem. Agents cannot access host files unless explicitly mounted.\n- **Virtual network.** The VM has no direct access to the host network. Outbound requests are proxied through the host with configurable controls.\n- **Process isolation.** No host process is visible or accessible from inside the VM.\n- **Per-actor containment.** Each actor is its own VM. Two actors share no filesystem, globals, module state, memory, or crash fate. The sidecar process that hosts those VMs may be shared by default as a performance optimization, but isolation is enforced at the VM level, not the host-process level.\n\n### Kernel-owned syscall paths\n\nEvery guest syscall is mediated by the kernel and checked against the runtime's permission policy. Concretely, the kernel mediates:\n\n- **Filesystem.** A virtual, in-memory filesystem. Guest reads and writes never reach the real host filesystem. Host data enters the VM only through the `files`, `mounts`, or `nodeModules` you configure explicitly. See [Filesystem](/docs/filesystem).\n- **Processes.** `node:child_process` spawns kernel-managed guest processes, never real host processes. Children can only run the commands the VM mounts (WASM-backed `sh` and coreutils, V8-backed `node`). See [Processes](/docs/processes).\n- **Network.** Guest `fetch()`, `node:http`, and raw sockets all flow through the kernel socket table. Guest `fetch()` runs through undici inside the isolate and then through the kernel socket table; it never opens a real host socket. See [Networking](/docs/networking).\n- **DNS, pipes, and PTYs** are likewise kernel-owned: no guest path reaches the host directly.\n- **Bindings.** Registered [bindings](/docs/bindings) are the only sanctioned way to hand the guest a named host capability. The guest invokes a binding by name with JSON input, the call round-trips to the host handler, and only the handler's return value comes back. The guest never receives the underlying host access.\n\n## What enters the VM\n\nThe host filesystem is never exposed to the guest by default. Host data crosses the boundary only through options you configure:\n\n- **`files`** seed bytes into the virtual filesystem. The bytes are copied in; the host path is never exposed.\n- **`mounts`** project a host directory at a guest path, Docker-style. The guest sees only the mounted subtree, read through the VFS lazily, never the wider host filesystem. Mounts are read-only unless you opt out.\n- **`nodeModules`** project a host `node_modules` directory (read-only, lazily) at a guest path so guest `import`/`require` resolves real installed packages.\n\nIn every case the guest sees only the subtree you mount, and writes to read-only mounts are rejected.\n\n## Permissions\n\nPermissions are the capability gate at the boundary. They merge over a secure default that denies the network and enables the filesystem, child processes, process info, and env. Because the merge is partial, you name only the scope you change.\n\n```ts\n// Grant network egress; everything else keeps the secure defaults.\npermissions: { network: \"allow\" }\n```\n\nA scope can be `\"allow\"`, `\"deny\"`, or a `{ default, rules }` policy that matches request patterns. Guest servers are reachable only over loopback inside the VM unless you exempt a port explicitly. See [Permissions](/docs/permissions) and [Networking](/docs/networking) for the full policy shape.\n\n## Resource and timing limits\n\nThe VM bounds guest execution so runaway or hostile code cannot hang or exhaust the host:\n\n- **Timeouts and cancellation** kill or cancel a run from the outside.\n- **Memory, CPU-time, and payload limits** are enforced by the VM.\n- **Timing-side-channel mitigation.** In the default mode, high-resolution clocks (`Date.now()`, `performance.now()`, `process.hrtime()`) are frozen within a run and `SharedArrayBuffer` is removed, to blunt timing side channels of the kind used in Spectre-style attacks.\n\nSee [Security & Auth](/docs/security-model) for resource limits, network control, and authentication setup.\n\n## What agentOS guarantees\n\n- Agent code cannot read or write host files outside configured mounts.\n- Agent code cannot make network requests except through the host proxy.\n- Agent code cannot access host environment variables or secrets.\n- Each actor's filesystem, sessions, and state are isolated from other actors.\n- Resource limits (CPU, memory) are enforced at the VM level.\n- A crash, resource exhaustion, or escape attempt is contained to a single VM; other VMs keep running, even when they share a sidecar process.\n\n## What you are responsible for\n\nThe boundary protects the host from the guest. It does **not** harden your host process against everything else. The VM alone is not enough without a hardened host, and a hardened host alone does not protect against code that runs with full host access inside your own process.\n\n- Hardening the host process and deployment environment. For internet-facing workloads that take untrusted input, run your host inside an already-hardened environment (for example AWS Lambda, Google Cloud Run, or a similar sandboxed platform).\n- Validating authentication tokens in `onBeforeConnect`.\n- Scoping [permissions](/docs/permissions) appropriately for your use case.\n- Managing API keys and secrets on the host side (use the [LLM gateway](/docs/llm-gateway) to avoid passing keys into the VM).\n- Configuring [resource limits and network controls](/docs/security-model) to match your threat model.\n- Choosing your blast radius: prefer a fresh VM per untrusted or high-risk task so an escape attempt cannot outlive a single VM.\n\n\u003CWarning>The boundary contains guest code, but you still own the host. Treat the host process as trusted infrastructure and harden it.\u003C/Warning>\n\n## Further reading\n\n- [Security configuration](/docs/security-model) for resource limits, network control, and authentication setup\n- [Permissions](/docs/permissions) for agent tool-use approval patterns\n- [agentOS vs Sandbox](/docs/versus-sandbox) for when to escalate to a full sandbox","src/content/docs/docs/security-model.mdx","e244aa6de3e29bdb","docs/sessions",{"id":242,"data":244,"body":247,"filePath":248,"digest":249,"deferredRender":16},{"title":245,"description":246,"skill":16},"Sessions","Create agent sessions, send prompts, stream responses, and subscribe to events.","Sessions launch an agent inside the VM, stream its responses in real time over `sessionEvent`, and persist a replayable ACP transcript that survives sleep/wake.\n\n## Create a session\n\nUse `createSession` to launch an agent inside the VM. Returns session metadata including capabilities and agent info. The agent starts in `/home/agentos` by default; override it with the `cwd` option below.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/create-session.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n### `createSession` options\n\nThe second argument to `createSession` accepts:\n\n- **`env`**: environment variables for the agent process (e.g. API keys). Not inherited from the host.\n- **`cwd`**: working directory inside the VM. Defaults to `/home/agentos`.\n- **`mcpServers`**: MCP servers (local child processes or remote URLs) exposing extra tools.\n- **`additionalInstructions`**: text appended to the agent's system prompt.\n- **`skipOsInstructions`**: skip the base OS instructions injection. Tool documentation is still included.\n\n## Send a prompt\n\nUse `sendPrompt` to send a message to an active session. The response contains the agent's reply.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/send-prompt.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Stream responses\n\nSubscribe to `sessionEvent` to receive real-time streaming output from the agent.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/stream-responses.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Cancel a prompt\n\nUse `cancelPrompt` to stop an in-progress prompt.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/cancel-prompt.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Close and destroy sessions\n\n- `closeSession` gracefully closes a session without removing persisted data\n- `destroySession` removes the session and all persisted data\n- To reconnect to a previously created session and replay its history, see [Replay events](#replay-events) and [Resuming a suspended session](/docs/architecture/agent-sessions#resuming-a-suspended-session)\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/close-destroy.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Runtime configuration\n\nChange model, mode, and thought level on a live session.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/runtime-config.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Replay events\n\nUse `getSessionEvents` to replay a session's persisted events, including for VMs that are not currently running. Pair it with `listPersistedSessions` to find earlier sessions.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/replay-events.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Persisted session history\n\nQuery session history from SQLite. Works even when the VM is not running.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/persisted-history.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Multiple sessions\n\nA single VM can run multiple sessions simultaneously. Each session has its own agent process but shares the same filesystem. Use different session IDs to manage them independently.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/multiple-sessions.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Agent logs\n\nThe agent (ACP adapter) runs as a process inside the VM. It uses **stdout** for ACP protocol traffic, so its **stderr** is the channel for logs, warnings, and crash diagnostics. Pass `onAgentStderr` to the VM to capture it, and route it to your own logger to see exactly what the agent is doing (or why it exited).\n\n\u003CNote>\n`onAgentStderr` is a VM-level option, so it covers every session's agent process. It's the fastest way to diagnose an agent that exits unexpectedly mid-turn; the crash reason surfaces here. If you omit it, chunks are written to the host `process.stderr` by default.\n\u003C/Note>\n\n\u003CCodeSnippet file=\"examples/sessions/server-logs.ts\" />","src/content/docs/docs/sessions.mdx","483d6169eed4df39","docs/software",{"id":250,"data":252,"body":255,"filePath":256,"digest":257,"deferredRender":16},{"title":253,"description":254,"skill":16},"Software","Install software packages and configure the commands available inside agentOS.","agentOS ships with a common set of POSIX utilities (coreutils, sed, grep, gawk, findutils, diffutils, tar, gzip) out of the box. The `software` option installs additional packages, each providing one or more CLI commands.\n\n## Install\n\n```bash\nnpm install @rivet-dev/agentos @agentos-software/pi\n```\n\nAdd packages like `@agentos-software/ripgrep` or `@agentos-software/jq` for anything beyond the default utilities. Browse the full catalog on the [Registry](/registry).\n\n## Usage\n\nImport the software packages you want, list them in the `software` array on the actor, then run commands through the client handle.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/software/server.ts\" />\n\n\u003CCodeSnippet file=\"examples/software/client.ts\" />\n\u003C/CodeGroup>\n\n## Available Packages\n\nBrowse all available software packages on the [Registry](/registry).\n\n## Custom Software\n\nPackage your own agents, command packages, and WASM commands. See [Software Definition](/docs/custom-software/definition) to define a package, and [Building Binaries](/docs/custom-software/building-wasm) to compile WASM commands from source in the [secure-exec registry](https://github.com/rivet-dev/secure-exec/tree/main/registry).","src/content/docs/docs/software.mdx","eae19d4d749a07f5","docs/versus-sandbox",{"id":258,"data":260,"body":263,"filePath":264,"digest":265,"deferredRender":16},{"title":261,"description":262,"skill":16},"agentOS vs Sandbox","When to use the lightweight agentOS VM, a full sandbox, or both together.","- **agentOS** is a lightweight VM that runs inside your process. Near-zero cold start, low memory, direct backend integration via [bindings](/docs/bindings).\n- **Sandboxes** are full Linux environments with root access, system packages, and native binary support.\n- **You can use both.** agentOS works with sandboxes through [sandbox mounting](/docs/sandbox). Agents run in the lightweight VM by default and spin up a full sandbox on demand.\n\n## Comparison\n\n| | agentOS VM | Full Sandbox |\n|---|---|---|\n| **Cost** | Very low. Runs in your process. | Pay per second of uptime. |\n| **Startup** | Near-zero cold start (~6 ms). | Seconds to spin up. |\n| **Backend integration** | Direct. [Bindings](/docs/bindings) call your functions with zero latency. | Indirect. Requires network calls back to your backend. |\n| **API keys** | Stay on the server via the [LLM gateway](/docs/llm-gateway). | Must be injected into the sandbox environment. |\n| **Permissions** | Granular, deny-by-default. | Coarse-grained (container-level). |\n| **Infrastructure** | `npm install` | Vendor account + API keys. |\n| **Best for** | Coding, file manipulation, scripting, API calls, orchestration. | Browsers, desktop automation, native compilation, dev servers. |\n\n## When to use each\n\n### agentOS VM\n\nUse the lightweight VM for most agent workloads:\n\n- Coding and file editing\n- Running scripts and CLI tools\n- Calling APIs and services via bindings\n- Multi-agent orchestration and workflows\n- Tasks where backend integration matters (permissions, tool access, LLM routing)\n\n### Full sandbox\n\nSpin up a sandbox when the workload needs a real Linux kernel:\n\n- Browsers and desktop automation (Playwright, Puppeteer, Selenium)\n- Heavy compilation and native toolchains\n- Dev servers with hot reload, databases, and system ports\n- GUI applications and VNC sessions\n\n### Both together\n\nUse agentOS with [sandbox mounting](/docs/sandbox) for workflows that need both:\n\n- Agent runs in the agentOS VM with full access to bindings and permissions\n- Sandbox spins up on demand for heavy tasks\n- Sandbox filesystem is mounted into the VM as a native directory\n- Agent reads and writes sandbox files the same way it reads local files","src/content/docs/docs/versus-sandbox.mdx","0ffcdc2908107d12","docs/webhooks",{"id":266,"data":268,"body":271,"filePath":272,"digest":273,"deferredRender":16},{"title":269,"description":270,"skill":16},"Webhooks","Trigger agent workflows from external webhooks using Hono and queues.","Use a lightweight HTTP server to receive webhooks and drive an agent. This example uses [Hono](https://hono.dev) to receive Slack webhooks and call an agent directly.\n\n## Example: Slack webhook to agent\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/webhooks/server.ts\" />\n\u003C/CodeGroup>\n\n## How it works\n\n1. Slack sends an HTTP POST to `/slack/events`\n2. The Hono handler validates the event and pushes it to the actor's queue\n3. The queue processes messages one at a time, creating agent sessions for each\n4. The agent responds and the worker posts the reply back to Slack\n\nThe queue provides backpressure and durability. If the agent is busy, messages wait in the queue. If the server restarts, queued messages are replayed.\n\n## Recommendations\n\n- Return `200` from the webhook handler immediately after queuing. External services like Slack have short timeout windows.\n- Store webhook secrets in environment variables, not in code.","src/content/docs/docs/webhooks.mdx","94ef640c5f0daa84","docs/system-prompt",{"id":274,"data":276,"body":279,"filePath":280,"digest":281,"deferredRender":16},{"title":277,"description":278,"skill":16},"System Prompt","How agentOS injects context into agent sessions.","agentOS automatically injects a system prompt into every agent session that describes the VM environment and available commands and bindings. The prompt is additive and never replaces the agent's own instructions (CLAUDE.md, AGENTS.md, etc.).\n\nThe base prompt is embedded in the sidecar (not written to a file inside the VM). At session start the sidecar assembles the base prompt with your additional instructions and generated binding docs, then injects the result into the agent adapter's launch arguments (for example, `--append-system-prompt` for Pi).\n\n## Customization\n\n- `additionalInstructions` appends extra instructions to the agent's system prompt. They are added after the base OS prompt and the generated binding docs, so they layer on top of (rather than replace) the agent's own instructions.\n- `skipOsInstructions` suppresses the base OS prompt while still injecting the generated binding docs.\n\n\u003CCodeSnippet file=\"examples/sessions/client.ts\" region=\"system-prompt\" />\n\n`additionalInstructions` can also be set globally in `agentOs({ options: { additionalInstructions } })` so it applies to every session.","src/content/docs/docs/system-prompt.mdx","4029572852f9303c","docs/agents/codex",{"id":282,"data":284,"body":288,"filePath":289,"digest":290,"deferredRender":16},{"title":285,"description":286,"skill":287},"Codex","Run the Codex coding agent inside a VM with skills, MCP servers, and custom configuration.",false,"## Quick start\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/codex/server.ts\" title=\"server.ts\" />\n\u003CCodeSnippet file=\"examples/codex/client.ts\" region=\"quickstart\" title=\"client.ts\" />\n\u003C/CodeGroup>\n\nRead [Sessions](/docs/sessions) first for session options, streaming events, prompts, and lifecycle management.\n\n## LLM Credentials\n\nSet the relevant variable(s) on the session's `env`, sourced from your server's environment:\n\n- `OPENAI_API_KEY` — OpenAI API key (built-in `openai` provider).\n- `OPENAI_BASE_URL` — route through a gateway or OpenAI-compatible endpoint.\n- Custom providers — defined in `~/.codex/config.toml`; each provider's `env_key` names the variable Codex reads for its key (e.g. `AZURE_OPENAI_API_KEY`, `MISTRAL_API_KEY`).\n\nSee [LLM Credentials](/docs/llm-credentials), and Codex's [config reference](https://developers.openai.com/codex/config-reference) for details.\n\n## Skills\n\nCodex discovers `SKILL.md` files from its skills directory. Write the skill into the VM before creating a session and Codex loads it automatically.\n\n\u003CCodeSnippet file=\"examples/codex/client.ts\" region=\"skills\" />\n\n## MCP servers\n\nExpose extra tools to the agent by passing `mcpServers` to `createSession`. Both local child-process servers and remote URLs are supported.\n\n\u003CCodeSnippet file=\"examples/codex/client.ts\" region=\"mcp\" />\n\n\u003CNote>\n**Pre-install `npx`-launched servers.** A local server started with `npx -y …` writes install progress to **stdout** on its first run, which corrupts the MCP stdio handshake (you'll see `Connection closed`). Pre-install it in the VM so `npx` is silent — `await agent.exec(\"npm install -g @modelcontextprotocol/server-filesystem\")` before the session — or pin the package and point `command` at the installed binary.\n\u003C/Note>\n\n## Customizing the agent\n\nCodex is a built-in agent, but it's just a software package under the hood. To ship your own ACP adapter, swap the underlying agent SDK, or register a tweaked build as a new agent, see [Custom Agents](/docs/agents/custom).","src/content/docs/docs/agents/codex.mdx","b5930de422b103ef","docs/agents/claude",{"id":291,"data":293,"body":296,"filePath":297,"digest":298,"deferredRender":16},{"title":294,"description":295,"skill":287},"Claude Code","Run the Claude Code agent inside a VM with skills, MCP servers, and custom configuration.","## Quick start\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/claude/server.ts\" />\n\n\u003CCodeSnippet file=\"examples/claude/client.ts\" title=\"client.ts\" region=\"quickstart\" />\n\u003C/CodeGroup>\n\nRead [Sessions](/docs/sessions) first for session options, streaming events, prompts, and lifecycle management.\n\n## LLM Credentials\n\nSet the relevant variable(s) on the session's `env`, sourced from your server's environment:\n\n- `ANTHROPIC_API_KEY` — Anthropic API key (direct API).\n- `ANTHROPIC_AUTH_TOKEN` — bearer token for proxy / OAuth auth.\n- `ANTHROPIC_BASE_URL` — route through a gateway or proxy endpoint.\n- `ANTHROPIC_MODEL` — override the default model.\n- `CLAUDE_CODE_USE_BEDROCK=1` — use Amazon Bedrock (auth via the AWS credential chain: `AWS_REGION`, `AWS_PROFILE`, …).\n- `CLAUDE_CODE_USE_VERTEX=1` — use Google Vertex AI (auth via Google Cloud credentials).\n\nSee [LLM Credentials](/docs/llm-credentials), and Claude Code's [environment variables](https://code.claude.com/docs/en/env-vars) for the full list.\n\n## Skills\n\nClaude Code discovers [agent skills](https://docs.claude.com/en/docs/claude-code/skills) from `SKILL.md` files under its skills directory. Write the skill into the VM before creating a session and Claude Code loads it automatically.\n\n\u003CCodeSnippet file=\"examples/claude/client.ts\" title=\"client.ts\" region=\"skill\" />\n\n## MCP servers\n\nExpose extra tools to the agent by passing `mcpServers` to `createSession`. Both local child-process servers and remote URLs are supported.\n\n\u003CCodeSnippet file=\"examples/claude/client.ts\" title=\"client.ts\" region=\"mcp\" />\n\n\u003CNote>\n**Pre-install `npx`-launched servers.** A local server started with `npx -y …` writes install progress to **stdout** on its first run, which corrupts the MCP stdio handshake (you'll see `Connection closed`). Pre-install it in the VM so `npx` is silent — `await agent.exec(\"npm install -g @modelcontextprotocol/server-filesystem\")` before the session — or pin the package and point `command` at the installed binary.\n\u003C/Note>\n\n## Customizing the agent\n\nClaude Code is a built-in agent, but it's just a software package under the hood. To ship your own ACP adapter, swap the underlying agent SDK, or register a tweaked build as a new agent, see [Custom Agents](/docs/agents/custom).","src/content/docs/docs/agents/claude.mdx","29e82ba8160cfbd0","docs/agents/custom",{"id":299,"data":301,"body":304,"filePath":305,"digest":306,"deferredRender":16},{"title":302,"description":303},"Custom Agents","Bring your own coding agent to agentOS by speaking the Agent Client Protocol (ACP) inside the VM.","A custom agent is a program that runs **inside the VM** to drive a coding agent. agentOS spawns it when you call `createSession()` and talks to it over the Agent Client Protocol. You ship it as a software package, exactly like the built-in agents.\n\n## Agent Client Protocol (ACP)\n\nagentOS speaks the [Agent Client Protocol (ACP)](https://agentclientprotocol.com) to every agent: JSON-RPC over stdio. The agent reads protocol messages on **stdin** and writes them on **stdout**, so stdout is reserved for ACP and **stderr is used for logs**. Your program only needs to speak ACP; how it runs the underlying model is up to you. See the [ACP documentation](https://agentclientprotocol.com) for the full protocol.\n\n## Two ways to build an agent\n\nThere are two shapes, depending on whether the agent runs in the ACP process or in its own.\n\n### Single process (embedded)\n\nThe ACP adapter **embeds the agent SDK** and runs it in the same process. One process inside the VM, lower memory footprint.\n\n\u003Csvg viewBox=\"0 0 340 246\" role=\"img\" aria-label=\"Single embedded process: one ACP adapter that embeds the agent, running inside the VM\" style=\"max-width:360px;width:100%;height:auto;display:block;margin:1.25rem auto;font-family:ui-sans-serif,system-ui,sans-serif;\">\n \u003Cdefs>\n \u003Cmarker id=\"ca-arrow-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0 0 L10 5 L0 10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"130\" y=\"18\" width=\"80\" height=\"28\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"170\" y=\"36\" text-anchor=\"middle\" font-size=\"11\" fill=\"#1b1916\">Host\u003C/text>\n \u003Cline x1=\"170\" y1=\"46\" x2=\"170\" y2=\"86\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#ca-arrow-1)\" />\n \u003Ctext x=\"184\" y=\"70\" font-size=\"10\" fill=\"#56524a\">ACP\u003C/text>\n \u003Crect x=\"50\" y=\"90\" width=\"240\" height=\"140\" rx=\"10\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" stroke-dasharray=\"4 3\" />\n \u003Ctext x=\"64\" y=\"110\" font-size=\"11\" fill=\"#56524a\">VM\u003C/text>\n \u003Crect x=\"80\" y=\"135\" width=\"180\" height=\"64\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"170\" y=\"163\" text-anchor=\"middle\" font-size=\"11\" fill=\"#1b1916\">ACP adapter +\u003C/text>\n \u003Ctext x=\"170\" y=\"180\" text-anchor=\"middle\" font-size=\"11\" fill=\"#1b1916\">agent (embedded)\u003C/text>\n\u003C/svg>\n\nFor example, an adapter to run **OpenCode**, which speaks ACP natively. One package is both the ACP process and the agent, so there's no separate adapter and nothing else is spawned.\n\n\u003CCodeSnippet file=\"examples/custom/opencode.ts\" />\n\n### ACP adapter (separate agent)\n\nThe ACP adapter is a thin **bridge** that spawns the real agent as its **own process** (a CLI or SDK) and translates between it and ACP. Full agent feature set, higher memory.\n\n\u003Csvg viewBox=\"0 0 340 276\" role=\"img\" aria-label=\"ACP adapter: a thin adapter inside the VM that spawns the agent as a separate process\" style=\"max-width:360px;width:100%;height:auto;display:block;margin:1.25rem auto;font-family:ui-sans-serif,system-ui,sans-serif;\">\n \u003Cdefs>\n \u003Cmarker id=\"ca-arrow-2\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0 0 L10 5 L0 10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"130\" y=\"18\" width=\"80\" height=\"28\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"170\" y=\"36\" text-anchor=\"middle\" font-size=\"11\" fill=\"#1b1916\">Host\u003C/text>\n \u003Cline x1=\"170\" y1=\"46\" x2=\"170\" y2=\"86\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#ca-arrow-2)\" />\n \u003Ctext x=\"184\" y=\"70\" font-size=\"10\" fill=\"#56524a\">ACP\u003C/text>\n \u003Crect x=\"50\" y=\"90\" width=\"240\" height=\"170\" rx=\"10\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" stroke-dasharray=\"4 3\" />\n \u003Ctext x=\"64\" y=\"110\" font-size=\"11\" fill=\"#56524a\">VM\u003C/text>\n \u003Crect x=\"90\" y=\"118\" width=\"160\" height=\"42\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"170\" y=\"144\" text-anchor=\"middle\" font-size=\"11\" fill=\"#1b1916\">ACP adapter\u003C/text>\n \u003Cline x1=\"170\" y1=\"160\" x2=\"170\" y2=\"180\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#ca-arrow-2)\" />\n \u003Ctext x=\"184\" y=\"174\" font-size=\"10\" fill=\"#56524a\">spawns\u003C/text>\n \u003Crect x=\"90\" y=\"182\" width=\"160\" height=\"46\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"170\" y=\"202\" text-anchor=\"middle\" font-size=\"11\" fill=\"#1b1916\">Agent process\u003C/text>\n \u003Ctext x=\"170\" y=\"217\" text-anchor=\"middle\" font-size=\"10\" fill=\"#56524a\">(CLI / SDK)\u003C/text>\n\u003C/svg>\n\nFor example, an adapter to run **Pi**: the `pi` CLI doesn't speak ACP, so `pi-acp` speaks ACP and spawns the CLI as a separate process. The packaged agent bundles both — its `agentos-package.json` names `pi-acp` as the `acpEntrypoint` and points it at the `pi` CLI via `agent.env`.\n\n\u003CCodeSnippet file=\"examples/custom/pi-cli.ts\" />\n\n## Use your agent\n\nRegister the package on the server with `software`. Sessions are then created from the client by `id`, exactly like any built-in agent.\n\n```ts title=\"server.ts\"\nimport { agentOS, setup, defineSoftware } from \"@rivet-dev/agentos\";\n\nconst myAgent = defineSoftware({\n packageDir, // the packaged agent directory; its agentos-package.json carries the agent block\n});\n\nconst vm = agentOS({ software: [myAgent] });\n\nexport const registry = setup({ use: { vm } });\nregistry.start();\n```\n\nSee [Sessions](/docs/sessions) for creating and driving sessions. Package your adapter with `agentos-toolchain pack --agent my-agent-acp` so its dependencies are bundled into the self-contained package directory and the `agent` block (naming the `bin/` ACP entrypoint) is written into the package's `agentos-package.json`, rather than shipping it as a loose file.\n\nAll built-in agents are defined exactly this way. Browse them for reference on [GitHub](https://github.com/rivet-dev/agentos/tree/main/registry/agent).\n\n## Read more\n\n- [Defining software packages](/docs/custom-software/definition): the full descriptor reference, including the `agentos-package.json` schema and every `agent` field (`acpEntrypoint`, `env`, `launchArgs`, `snapshot`).\n- [Building binaries](/docs/custom-software/building-wasm): compile WASM command binaries and use the registry.\n\n## Debugging\n\nWhen a custom agent exits mid-turn or a tool call fails, capture the agent's stderr with the `onAgentStderr` hook on `AgentOs.create()`. The agent uses stdout for ACP, so stderr carries its logs and crash output. See [Debugging](/docs/debugging) for that hook and the runtime (sidecar) logs.","src/content/docs/docs/agents/custom.mdx","5a17ae5b203743cc","docs/workflows",{"id":307,"data":309,"body":312,"filePath":313,"digest":314,"deferredRender":16},{"title":310,"description":311,"skill":16},"Workflow Automation","Orchestrate multi-step agent tasks with durable workflows.","Orchestrate multi-step agent tasks with durable workflows that survive crashes and restarts. Build them with RivetKit's `workflow()` run handler, where each `ctx.step()` is recorded, retried, and resumed independently, and the output of one step can feed into the next.\n\n## Basic workflow\n\nA workflow is the durable `run` handler of an actor. Wrap it in `workflow()` and drive a multi-step agent task as an ordered series of steps: clone the repo, let an agent fix the bug, then run the tests. Trigger work by sending to a queue; the workflow loops and waits durably for the next message.\n\nSession creation and prompting happen within the step that uses them, so a session never has to outlive the work it backs (sessions are ephemeral and would not survive a replay). Steps reach the agentOS VM, a separate actor, through `ctx.client()`.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/workflows/server.ts\" region=\"basic\" title=\"server.ts\" />\n\u003CCodeSnippet file=\"examples/workflows/client.ts\" title=\"client.ts\" />\n\u003C/CodeGroup>\n\n## Agent chaining\n\nOutput of one agent session feeds into the next. Each session is created and completed within its own step, and data passes between steps through the VM filesystem (a review file) and step return values.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/workflows/server.ts\" region=\"chaining\" title=\"server.ts\" />\n\u003CCodeSnippet file=\"examples/workflows/chaining-client.ts\" title=\"client.ts\" />\n\u003C/CodeGroup>\n\n## Recommendations\n\n- Build the actor's `run` handler with `workflow()` so each `ctx.step()` is durable: recorded, retried, and resumed independently across crashes and restarts.\n- Keep step names stable across code changes. Renaming a step breaks replay for in-progress workflows.\n- Create and close sessions within the step that uses them. Sessions are ephemeral, so keep their lifetime scoped to one unit of work.\n- Pass data between steps via the filesystem or step return values, not session state.\n- Keep `state` changes and other actor-local side effects inside `ctx.step()` callbacks; use non-step workflow code (queue waits, loops, sleeps) only for orchestration.\n- Reach the agentOS VM, a separate actor, from inside a step with `ctx.client()`.\n- See [Workflows](https://rivet.dev/docs/actors/workflows) for the full workflow API reference including timers, joins, and races.","src/content/docs/docs/workflows.mdx","e67a320bd6af7501","docs/agents/opencode",{"id":315,"data":317,"body":320,"filePath":321,"digest":322,"deferredRender":16},{"title":318,"description":319,"skill":287},"OpenCode","Run the OpenCode coding agent inside a VM with skills, MCP servers, and custom configuration.","## Quick start\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/opencode/server.ts\" />\n\n\u003CCodeSnippet file=\"examples/opencode/client.ts\" title=\"client.ts\" region=\"quickstart\" />\n\u003C/CodeGroup>\n\nRead [Sessions](/docs/sessions) first for session options, streaming events, prompts, and lifecycle management.\n\n## LLM Credentials\n\nOpenCode auto-detects a provider when its key is present on the session's `env`, sourced from your server's environment. Common variables:\n\n- `ANTHROPIC_API_KEY` — Anthropic (Claude), the default.\n- `OPENAI_API_KEY` — OpenAI.\n- `OPENROUTER_API_KEY` — OpenRouter.\n- `GEMINI_API_KEY` — Google Gemini.\n- `GROQ_API_KEY` — Groq.\n- …plus Amazon Bedrock, Azure, Google Vertex, and 70+ providers via [models.dev](https://models.dev).\n\nSee [LLM Credentials](/docs/llm-credentials), and OpenCode's [providers docs](https://opencode.ai/docs/providers/) for the full list.\n\n## Model configuration\n\nTo pin a specific model — or point a provider at a custom endpoint — write an OpenCode config file into the VM before creating the session. OpenCode reads `\u003CHOME>/.config/opencode/opencode.json` (the agent's `HOME` is `/home/agentos` by default).\n\n\u003CWarning>\nTwo settings will silently produce an **empty response** if wrong:\n- The Anthropic provider **`baseURL` must end in `/v1`** (`https://api.anthropic.com/v1`). Without `/v1`, OpenCode calls `…/messages` and Anthropic returns `404 Not Found`.\n- The **`model` must be a current model id.** A retired id returns a `404 not_found_error` and the turn ends with zero output.\n\u003C/Warning>\n\n```ts\n// Write the config before creating the session\nawait agent.mkdir(\"/home/agentos/.config/opencode\", { recursive: true });\nawait agent.writeFile(\n \"/home/agentos/.config/opencode/opencode.json\",\n JSON.stringify({\n $schema: \"https://opencode.ai/config.json\",\n model: \"anthropic/claude-haiku-4-5-20251001\", // use a current model id\n provider: {\n // The Anthropic baseURL MUST include /v1, or requests 404.\n anthropic: { options: { baseURL: \"https://api.anthropic.com/v1\" } },\n },\n }),\n);\n\nconst session = await agent.createSession(\"opencode\", {\n env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },\n});\n```\n\n## Skills\n\nOpenCode discovers `SKILL.md` files from its skills directory. Write the skill into the VM before creating a session and OpenCode loads it automatically.\n\n\u003CCodeSnippet file=\"examples/opencode/client.ts\" region=\"skills\" />\n\n## MCP servers\n\nExpose extra tools to the agent by passing `mcpServers` to `createSession`. Both local child-process servers and remote URLs are supported.\n\n\u003CCodeSnippet file=\"examples/opencode/client.ts\" region=\"mcp\" />\n\n\u003CNote>\n**Pre-install `npx`-launched servers.** A local server started with `npx -y …` writes install progress to **stdout** on its first run, which corrupts the MCP stdio handshake (you'll see `Connection closed`). Pre-install it in the VM so `npx` is silent — `await agent.exec(\"npm install -g @modelcontextprotocol/server-filesystem\")` before the session — or pin the package and point `command` at the installed binary.\n\u003C/Note>\n\n## Customizing the agent\n\nOpenCode is a built-in agent, but it's just a software package under the hood. To ship your own ACP adapter, swap the underlying agent SDK, or register a tweaked build as a new agent, see [Custom Agents](/docs/agents/custom).","src/content/docs/docs/agents/opencode.mdx","055f29f39ce81de3","docs/agents/pi",{"id":323,"data":325,"body":328,"filePath":329,"digest":330,"deferredRender":16},{"title":326,"description":327,"skill":16},"Pi","Run the Pi coding agent inside a VM with extensions and custom configuration.","## Quick start\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/pi/server.ts\" />\n\n\u003CCodeSnippet file=\"examples/pi/client.ts\" region=\"quick-start\" />\n\u003C/CodeGroup>\n\nRead [Sessions](/docs/sessions) first for session options, streaming events, prompts, and lifecycle management.\n\n## LLM Credentials\n\nSet the relevant variable on the session's `env`, sourced from your server's environment:\n\n- `ANTHROPIC_API_KEY` — Anthropic (Claude), the default.\n- Other providers — use the provider-named key (e.g. `OPENAI_API_KEY`, `GEMINI_API_KEY`, `OPENROUTER_API_KEY`).\n\nSee [LLM Credentials](/docs/llm-credentials), and Pi's [providers docs](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/providers.md) for the full list.\n\n## Skills\n\nPi discovers `SKILL.md` files from its skills directory. Write the skill into the VM before creating a session and Pi loads it automatically.\n\n\u003CCodeSnippet file=\"examples/pi/client.ts\" region=\"skill\" />\n\n## MCP servers\n\nExpose extra tools to the agent by passing `mcpServers` to `createSession`. Both local child-process servers and remote URLs are supported.\n\n\u003CCodeSnippet file=\"examples/pi/client.ts\" region=\"mcp\" />\n\n\u003CNote>\n**Pre-install `npx`-launched servers.** A local server started with `npx -y …` writes install progress to **stdout** on its first run, which corrupts the MCP stdio handshake (you'll see `Connection closed`). Pre-install it in the VM so `npx` is silent — `await agent.exec(\"npm install -g @modelcontextprotocol/server-filesystem\")` before the session — or pin the package and point `command` at the installed binary.\n\u003C/Note>\n\n## Extensions\n\nPi supports [extensions](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent/examples/extensions) that let you register custom tools, modify the system prompt, and hook into agent lifecycle events. Write a `.js` file into the VM's extensions directory before creating a session and Pi discovers it automatically.\n\nPi scans two directories for `.js` extension files:\n\n| Directory | Scope |\n|-----------|-------|\n| `~/.pi/agent/extensions/` | Global — applies to all Pi sessions |\n| `\u003Ccwd>/.pi/extensions/` | Project — applies only when cwd matches |\n\n\u003CCodeSnippet file=\"examples/pi/client.ts\" region=\"extension\" />\n\nSee the [Pi extension documentation](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent/examples/extensions) for the full extension API.\n\n## Customizing the agent\n\nPi is a built-in agent, but it's just a software package under the hood. To ship your own ACP adapter, swap the underlying agent SDK, or register a tweaked Pi build as a new agent, see [Custom Agents](/docs/agents/custom).","src/content/docs/docs/agents/pi.mdx","754e14625aa08f9a","docs/architecture/agent-sdk-snapshots",{"id":331,"data":333,"body":336,"filePath":337,"digest":338,"deferredRender":16},{"title":334,"description":335,"skill":16},"Agent SDK Snapshots","How an agent's SDK is evaluated once per sidecar into a V8 heap snapshot and reused across sessions instead of re-imported on every createSession: the bundle, the userland snapshot, the process-wide cache, pre-warm, per-session restore, isolation, and the snapshot-safety rules an SDK must follow.","This page is an internals deep-dive on **agent SDK snapshotting** — an optional optimization that loads an agent's SDK *once per sidecar* and reuses it for every session, instead of re-evaluating the whole SDK module graph on each `createSession`. For the agent-author view (how to opt in and the rules your SDK must follow), see [Software Definition → SDK snapshotting & snapshot-safety](/docs/custom-software/definition). For how sessions work in general, see [Agent Sessions](/docs/architecture/agent-sessions).\n\n## The problem: per-session SDK re-evaluation\n\nWhen a session starts, its agent adapter runs inside a fresh [V8 isolate](/docs/architecture/agent-sessions) and imports the agent SDK (for Pi, `@mariozechner/pi-coding-agent`). Importing a real-world SDK means resolving, loading, compiling, and **evaluating** a large module graph — hundreds of modules running their top-level initialization. That evaluation dominates session-creation latency, and because every session gets a fresh isolate, it is paid *again on every `createSession`*.\n\nThe work is identical every time: the same modules, evaluated to the same post-init heap, only to be thrown away when the session ends. Snapshotting captures that post-init heap once and stamps it into every new isolate.\n\n## How V8 heap snapshots work here\n\nagentOS already boots every guest isolate from a **V8 startup snapshot** of the runtime bridge (the polyfill layer that provides `fetch`, node builtins, the kernel-backed module loader, etc.). A startup snapshot is a serialized image of a V8 heap *after* some code has run; restoring it into a fresh isolate reproduces that heap by deserialization rather than re-execution, and each restore produces an independent context.\n\nAgent SDK snapshotting extends that same mechanism: it evaluates the agent SDK **into the same snapshot context, right after the bridge**, so the captured image contains the bridge *and* the fully-initialized SDK. Restoring it gives a fresh isolate where the SDK is already present — no import, no evaluation.\n\n## Where it sits in the component model\n\nThe [three components](/docs/architecture#the-big-picture) are unchanged. Snapshotting only changes *how* the executor's isolate is seeded:\n\n- **Client.** Builds the SDK bundle at package-build time and passes it to the sidecar as trusted VM configuration (`jsRuntime.snapshotUserlandCode`). It decides which agents opt in.\n- **Sidecar.** Owns the snapshot. It builds the snapshot once, caches it process-wide, and seeds each session's isolate from it. The SDK runs inside the isolate under the same [permission policy](/docs/permissions) as any guest code — snapshotting grants the SDK no extra capability.\n- **Executor.** The agent adapter restores into an isolate where the SDK is already on the global, reads it, and proceeds. It is untrusted guest code as always.\n\n## The pipeline\n\n### 1. Bundle the SDK to one snapshottable unit\n\nThe agent SDK and its transitive dependencies are bundled (esbuild, IIFE) into a single file shipped with the agent package (`dist/sdk-snapshot.js`). The bundle evaluates the SDK and publishes its public API on a well-known global (e.g. `globalThis.__PI_SDK_RUNTIME__`). Node builtins stay external (resolved by the bridge's in-snapshot polyfills); heavy provider SDKs that are only reached via dynamic `import()` stay lazy and load post-restore from the VFS.\n\n### 2. Capture the evaluated SDK into the snapshot\n\nThe sidecar runs the bridge, then the SDK bundle, in one snapshot-creation context, then serializes the heap. The result is a startup blob containing both. Per-session configuration (cwd, model, API keys) is **not** captured — it is injected after restore, so one blob serves every session.\n\n### 3. Cache it process-wide, keyed by content\n\nThe blob is stored in a **sidecar-process-wide cache keyed by `sha256(bridge + bundle)`**. Any change to the bridge or the bundled dependency graph changes the key and triggers exactly one rebuild; an unchanged bundle is a cache hit. This is what makes it **build-once-per-sidecar**: the cache is shared across every VM and session in the process.\n\n### 4. Pre-warm so the first session is warm\n\nBuilding the snapshot is itself the expensive evaluation, just done once. To keep it off the session-create critical path, the sidecar **pre-warms** at VM creation: when a VM is configured with a snapshot bundle, the sidecar builds the blob into the cache *before* the first session is created. The first session then restores from a warm cache like every session after it.\n\n\u003CWarning>\n**Pre-warm and V8 initialization order**\nThe V8 platform must be initialized on a long-lived thread *before* any pre-warm runs. agentOS initializes the embedded runtime on the sidecar's main thread at startup. Initializing V8 lazily on a transient worker thread that then exits corrupts the platform and wedges later isolate creation — so the startup init is load-bearing, not incidental.\n\u003C/Warning>\n\n### 5. Restore a fresh isolate per session\n\nOn `createSession`, the agent's isolate is created from the cached blob. The SDK is already evaluated in the snapshot's default context, and each session gets a **fresh context cloned from it**. The adapter reads the SDK off the global instead of importing it. If no snapshot is configured — or snapshot creation fails — the adapter transparently falls back to the per-session dynamic-import path, so snapshotting never affects correctness, only latency.\n\n## Isolation\n\nEach session leases a **fresh context** cloned from the snapshot's default context. The captured SDK is shared read-only through the blob, but live state is not: a global, a captured-object mutation, or even a built-in prototype change made in one session is **not** observable in another. This is the same isolation guarantee as a non-snapshot session — a fresh isolate per session — and is covered by dedicated tests (a session that mutates `globalThis`, an SDK object, and `Array.prototype`, with a second session from the same snapshot seeing none of it).\n\n## Snapshot-safety: what an SDK must avoid\n\nA startup snapshot can only capture a pure JS heap. The SDK's **module-initialization** code (everything that runs at import time, before any function is called) must not, at top level:\n\n- Create a **native handle** — load a `.node` addon, instantiate WebAssembly, or produce any V8 *External* object (for example an ICU-backed `Intl.Segmenter` singleton). These cannot be serialized and abort snapshot creation.\n- Open an **fd, socket, timer, or worker**, or leave a **pending promise** at the end of evaluation.\n- Read **non-deterministic or per-session state** (`process.env`, cwd, model, `Date.now()`, `Math.random()`, a random UUID) into a module constant — it would be frozen to the build-time value.\n\nReal-world SDKs frequently break these by accident. agentOS makes such an SDK snapshottable with **build-time transforms in the bundle** that defer the offending work to first use (e.g. wrap a module-level native singleton in a lazy proxy, inline a top-level config-file read, convert eager fire-and-forget imports to synchronous module references). Each transform asserts the source shape it expects, so an upstream SDK change surfaces as a build error rather than a silent regression. The full author-facing rules live in the [Software Definition](/docs/custom-software/definition) reference.\n\n## Opt-in, per agent\n\nSnapshotting is **opt-in per agent**, via `agent.snapshot: true` on the [agent software descriptor](/docs/custom-software/definition), and requires the agent package to ship a snapshot-safe `dist/sdk-snapshot.js`. Today only the Pi agent opts in; other agents run the normal per-session import path. An agent qualifies by (1) being snapshot-safe and (2) building the bundle — there is nothing Pi-specific in the runtime mechanism.\n\n\u003CNote>\n**Current trade-off**\nThe bundle is currently delivered inline in the (trusted) VM config, which moves bytes onto VM creation. The intended refinement is to ship the bundle as a build-time blob the sidecar loads from the guest VFS by path, keeping the config small. Either way the snapshot is built once per sidecar and reused across sessions.\n\u003C/Note>","src/content/docs/docs/architecture/agent-sdk-snapshots.mdx","eac9412e8bdd154e","docs/architecture/agent-sessions",{"id":339,"data":341,"body":344,"filePath":345,"digest":346,"deferredRender":16},{"title":342,"description":343,"skill":16},"Agent Sessions","Internals of agent sessions: how a session is created and bound to a VM, how prompts and events flow from client to sidecar to agent adapter and back, the session lifecycle, and where session state lives.","This page is an internals deep-dive on how agent sessions work under the hood. For the usage API (creating sessions, sending prompts, streaming responses, replaying events), see [Sessions](/docs/sessions).\n\nA session is a long-lived conversation with an agent (such as [Pi](https://github.com/mariozechner/pi-coding-agent)) running inside a VM. Where a bare `exec()` / `run()` starts a fresh guest process and returns when it exits, a session keeps an agent process alive across many prompts, streams its output back as events, and persists a transcript that survives sleep/wake cycles. Everything below describes the machinery that makes that possible while keeping the agent inside the same isolation boundary as any other guest.\n\n## Where a session sits in the component model\n\nThe [three components](/docs/architecture#the-big-picture) are unchanged for sessions: a trusted **client**, the trusted **sidecar** that owns the kernel, and the untrusted **executor** that runs guest code. An agent session adds one more layer on the guest side of the boundary:\n\n- **Client.** Calls `createSession`, `sendPrompt`, and the rest of the session API. It never runs the agent itself; it drives the session over the wire protocol.\n- **Sidecar / kernel.** Spawns the agent as a kernel-managed process inside the VM, owns the session's I/O, applies the permission policy on every syscall the agent makes, and persists the transcript.\n- **Agent adapter.** A per-agent-type shim, inside the VM, that translates between the session protocol and the specific agent's native interface. It normalizes the agent's output into the [Agent Communication Protocol (ACP)](/docs/sessions) so every agent type produces the same event shape.\n- **Executor.** The agent process itself plus any tools it spawns. It is untrusted guest code like any other: its file reads, child processes, and network calls all flow through the kernel.\n\nThe key consequence: an agent is not privileged. It is a guest process that happens to be long-lived and conversational. Its capabilities are exactly the VM's [permission policy](/docs/permissions), nothing more.\n\n## Creating a session and binding it to a VM\n\nA session is always created against an existing VM. The client resolves a VM handle (for example `client.vm.getOrCreate([...])`) and calls `createSession(agentType, options)` on it. Under the hood:\n\n1. **The request crosses the wire** (client to sidecar) carrying the agent type and the session options: `env`, `cwd`, `mcpServers`, `additionalInstructions`, and `skipOsInstructions`.\n2. **The kernel boots the VM** if it is not already running, with its bootstrapped virtual filesystem.\n3. **The sidecar selects the agent adapter** for the requested type and spawns the agent as a kernel-managed process inside that VM. Because the VM does not inherit the host `process.env`, the agent only sees the `env` passed in the options (this is why API keys must be supplied explicitly). The process starts in `cwd` (default `/home/agentos`).\n4. **The session is registered** in the VM with a `sessionId`, and the adapter performs its handshake with the agent to discover `capabilities` and `agentInfo`.\n5. **The handle returns** that metadata to the client.\n\nThe session is bound to that VM for its lifetime. The agent process, its working directory, and its persisted transcript all live inside the VM's isolation domain. A VM can host several sessions at once: each gets its own agent process, but they share the one VM's filesystem (see [Multiple sessions](/docs/sessions#multiple-sessions)). Two sessions in two different VMs share nothing, exactly as described in the [isolation model](/docs/architecture).\n\n\u003CNote>MCP servers configured on a session follow the same boundary. A `local` MCP server runs as a child process inside the VM (kernel-managed, gated by the permission policy); a `remote` MCP server is reached over the network, so its traffic flows through the kernel socket table and is subject to the network allowlist.\u003C/Note>\n\n## How a prompt flows: client to agent and back\n\nSending a prompt is a request in one direction with a stream of events flowing back in the other. The lifecycle of a single prompt extends the general [lifecycle of a request](/docs/architecture):\n\n1. **The client calls `sendPrompt(sessionId, text)`.** The request crosses the wire to the sidecar (hop one).\n2. **The sidecar routes it to the session's agent adapter,** which translates the prompt into the agent's native input and writes it to the running agent process.\n3. **The agent works the turn.** As it thinks, calls tools, edits files, and produces output, every action it takes is a guest syscall back into the kernel (hop two): file reads/writes hit the VFS, tool subprocesses are kernel-managed, and network calls go through the socket table under the allowlist.\n4. **The adapter normalizes the agent's output into ACP events.** Each event is assigned a monotonically increasing sequence number, appended to the session's event log, and persisted.\n5. **Events stream back to the client** as `sessionEvent` notifications, carrying the `sessionId` and the ACP `event` (its `method` and `params`). This is why the docs recommend subscribing to `sessionEvent` before calling `sendPrompt`: events emitted early in the turn would otherwise be missed.\n6. **The turn resolves.** When the agent finishes the turn, `sendPrompt` resolves with the reply.\n\n`cancelPrompt(sessionId)` interrupts an in-progress turn: the request crosses to the sidecar, which signals the agent process to stop the current turn through the adapter, leaving the session itself alive for the next prompt.\n\n```\nclient sidecar / kernel agent adapter agent (executor)\n | sendPrompt | | |\n | --------------------> | route to session | |\n | | ---------------------------> | native input ---> |\n | | | | (thinks, calls\n | | \u003C--- syscalls (VFS, procs, sockets) ------------ | tools, edits)\n | | | \u003C-- native output |\n | sessionEvent (ACP) | persist + assign seq | |\n | \u003C------------------- | \u003C-------- ACP events ------- | |\n | ...stream... | | |\n | resolve(reply) | | |\n | \u003C------------------- | | |\n```\n\n## Session lifecycle\n\nA session moves through these states, all driven by client calls over the wire:\n\n- **Active.** Created and bound to a running VM, with a live agent process. Prompts can be sent and events stream back.\n- **Suspended.** When the VM sleeps, the agent process is torn down but the session's persisted transcript remains in storage. `resumeSession(sessionId)` reconnects: the kernel wakes the VM, re-spawns the agent, and rebinds the session so prompts can continue.\n- **Closed.** `closeSession(sessionId)` gracefully shuts down the agent process and releases its in-VM resources, but leaves the persisted transcript intact so history can still be queried.\n- **Destroyed.** `destroySession(sessionId)` removes the session and all of its persisted events. This is irreversible.\n\nBecause the transcript is persisted, closing or suspending a session is not the same as losing it: the event history can be read back later, even when the VM is not running.\n\nRuntime configuration (`setModel`, `setMode`, `setThoughtLevel`) mutates an active session in place by sending the change through the adapter to the live agent, without restarting the session.\n\n## Resuming a suspended session\n\nWhen a VM sleeps the agent process is destroyed, but the session registry and the transcript survive in SQLite. `resumeSession(sessionId)` (or simply prompting a suspended session) rebinds the stable, client-facing `sessionId` to a freshly spawned agent. Resume is **lazy** (it runs on the first prompt to a non-live session) and **capability-driven** (the orchestrator never special-cases an agent by name, only by what it advertises). There are two paths:\n\n- **Native ACP resume (optimization).** If the agent advertises ACP `loadSession`/`resume` and its own store survived on the durable root, the sidecar issues `session/load` and the agent restores its full context itself. The `sessionId` is unchanged.\n- **Universal transcript fallback.** If the agent has no native resume, or its store did not survive, the sidecar reconstructs a Markdown transcript from the recorded events, writes it into the VM (for example `/root/.agentos/threads/\u003CsessionId>.md`), starts a fresh agent, and prefixes the next prompt with a pointer to that file. Because this needs only file-read tools it works for any agent with no per-agent code, at the cost of pointing the agent at the transcript rather than pre-loading it into context.\n\nResume depends on a **durable root filesystem**. The RivetKit actor configures one automatically (its SQLite-backed root), so transcript capture and resume work out of the box. A direct `AgentOs` SDK user on the default in-memory root has no durable store: transcript capture is a no-op and context cannot be restored, so configure a durable root explicitly if you need resume outside the actor.\n\n## Adapter crashes and bounded auto-restart\n\nIf the agent process exits without `closeSession()` — any spontaneous exit, including exit code 0 — the sidecar treats it as a crash, not a lifecycle transition. Crashes are detected both mid-request (the exchange loop observes the process exit) and while idle (the next write to the dead adapter fails). The sidecar logs the exit with its code and a stderr tail, emits an `AcpAgentExitedEvent` to the host (`onAgentExit` in the SDK, `agentCrashed` on the actor), and attempts an **in-place restart, bounded to 3 attempts per session**:\n\n- **Native re-attach only.** The restart relaunches the adapter with the exact parameters of the original launch, re-probes capabilities with a fresh `initialize`, and — if the agent advertises `loadSession`/`resume` — re-attaches the **same `sessionId`** via `session/load`. Clients keep their handle and the session stays active. There is deliberately no `session/new` fallback tier here: a fallback would produce a different live session id that an in-place restart cannot remap transparently. That path belongs to lazy resume (above), which owns the external→live remap.\n- **Eviction otherwise.** If the adapter has no native resume capability, the restart fails, or the budget is exhausted, the session record is evicted — the same teardown as before auto-restart existed. The persisted transcript is untouched, so the actor's lazy resume can still recover the conversation on the next prompt.\n- **The interrupted request still fails.** A prompt in flight when the adapter died is never replayed (the turn may have had side effects); its error names the restart outcome so the caller knows whether a retry will succeed.\n\nEach event carries `{ exitCode, restart, restartCount, maxRestarts }`, where `restart` is `\"restarted\"`, `\"unsupported\"`, `\"failed\"`, or `\"exhausted\"`; only `\"restarted\"` leaves the session usable. See [Debugging](/docs/debugging#agent-crashes-onagentexit) for capturing these from the SDK.\n\n## Where session state lives\n\nSession state spans two tiers:\n\n- **In-memory, while the VM runs.** The running agent process holds the live conversation, and the sidecar keeps the session's recent event log with sequence numbers, the basis for live reconnection: a client tracks the last sequence number it processed and asks for everything after it, so no events are dropped or duplicated across a reconnect.\n- **Persisted in SQLite, independent of the VM.** Every ACP event is written to a SQLite-backed transcript store inside the VM, keyed by `sessionId` and sequence number. This tier survives sleep/wake and VM shutdown. `listPersistedSessions()` and `getSessionEvents(sessionId)` read from it and work even when the VM is not running, which is what makes transcript-history UIs possible without keeping a VM warm.\n\nSee [Replay events](/docs/sessions#replay-events) for replaying a session's persisted events.\n\nThe transcript living inside the VM keeps session state on the same side of the boundary as the agent that produced it: it is part of the VM's isolation domain, not the client's. The client only ever sees it by asking the sidecar for it over the wire.\n\n## Where to go next\n\n- [Sessions](/docs/sessions): the usage API for creating sessions, sending prompts, and replaying events.\n- [Architecture](/docs/architecture): the component model, request lifecycle, and isolation model that sessions build on.\n- [Permissions](/docs/permissions): the policy the kernel enforces on every syscall an agent makes.\n- [Replay events](/docs/sessions#replay-events): in-memory versus persisted event replay.","src/content/docs/docs/architecture/agent-sessions.mdx","6f588d17c4849062","docs/architecture/compiler-toolchain",{"id":347,"data":349,"body":352,"filePath":353,"digest":354,"deferredRender":16},{"title":350,"description":351,"skill":16},"Compiler Toolchain","How agentOS compiles its command suite to WebAssembly: Rust coreutils via cargo and C programs via wasi-sdk, linked against a patched wasi-libc plus the wasi-ext bindings, and how the resulting .wasm files become the guest's commands.","The commands a guest runs through [process execution](/docs/processes), the shell\n(`sh`) and the coreutils behind it, are not native host binaries. They are\nWebAssembly modules compiled ahead of time and mounted into the VM. This page\ncovers how that command suite is produced: which toolchains compile it, what it\nlinks against, and how the resulting `.wasm` files become the guest's commands.\n\nFor *why* WASM is a first-class guest and *how* it presents a POSIX surface at\nruntime, see the [WASM VM](/docs/architecture/posix-syscalls) page. This page is the build-side\ncounterpart: it documents the toolchain that emits binaries carrying both\n[the host-import layer and the WASI shim](/docs/architecture/posix-syscalls).\n\n## Target: `wasm32-wasip1`\n\nEverything in the command suite is compiled to a single target,\n`wasm32-wasip1`: the WASI preview 1 ABI on the 32-bit WebAssembly architecture.\nPicking one target for the whole suite means a single libc, a single set of\nhost import declarations, and a single runtime shim can serve every command.\n\nA guest module built for this target expects standard WASI (preopened file\ndescriptors, clocks, randomness, file I/O) plus the extra agentOS import modules\ndescribed below. Both halves are satisfied at runtime by the kernel-backed\nruntime; nothing in a compiled command reaches a real host syscall.\n\n## Two source languages, two compilers\n\nThe suite is heterogeneous: most tools are Rust, some are C. Each language uses\nits own compiler driver, but both emit the same `wasm32-wasip1` ABI and link\nagainst the same sysroot, so the outputs are interchangeable at runtime.\n\n- **Rust coreutils** are built with **`cargo`** targeting `wasm32-wasip1`. Rust's\n standard library already has first-class support for this target, so the\n coreutils crates compile with an ordinary cross-compile invocation.\n- **C programs** are built with the **`wasi-sdk`** toolchain, a packaged\n `clang` plus sysroot tuned for WASI. C tools that have no Rust equivalent (or\n that are easier to carry as upstream C) go through this path.\n\n```bash\n# Rust coreutils\ncargo build --target wasm32-wasip1 --release\n\n# C programs via wasi-sdk, linked against the patched libc + wasi-ext\n$WASI_SDK/bin/clang --target=wasm32-wasip1 \\\n --sysroot=$WASI_SYSROOT \\\n -lwasi-ext \\\n tool.c -o tool.wasm\n```\n\n## What every binary links against\n\nRegardless of source language, each command links against the same two pieces.\nTogether they give a single binary both the standard WASI calls and the agentOS\nprocess / user / network extensions.\n\n- **A patched `wasi-libc`.** The libc is the WASI standard library, modified so\n that the calls a normal command-line program performs resolve against the\n agentOS surface instead of failing or hitting unimplemented stubs. This is the\n same patched libc the [Layer 2 shim](/docs/architecture/posix-syscalls) adapts at runtime; the\n build side and the runtime side are two ends of the same contract.\n- **The `wasi-ext` bindings.** These declare the extra WebAssembly import\n modules (`host_process`, `host_user`, `host_net`, and the small\n `host_sleep_ms` binding) that base WASI cannot express. Linking `wasi-ext`\n into a binary is what lets its libc emit `fork` / `exec`, `getuid` / `getgid`,\n and `connect` / `listen` as ordinary-looking syscalls that the host runtime\n then services through the kernel. See\n [Layer 1: custom host import modules](/docs/architecture/posix-syscalls) for the runtime half.\n\n\u003CNote>\nThe import declarations are compile-time only: linking `wasi-ext` tells the\nmodule *which* host imports to reference, but the calls are still routed through\nthe kernel and gated by the VM's [permission policy](/docs/permissions) at\nruntime. Building against `host_net` does not grant network access.\n\u003C/Note>\n\n## From `.wasm` to a guest command\n\nThe compiler toolchain's product is a set of `.wasm` files, one per command.\nThose files are what the runtime mounts as the guest's executables: when a guest\ninvokes `ls`, `sh`, or any other bundled tool, the kernel resolves the name to\nthe corresponding module, instantiates it with the host imports and the WASI\nshim wired in, and runs it as a [child process](/docs/processes) with real\nprocess, user, and network semantics, all virtualized.\n\nThe same path is open to your own programs. A program you compile for\n`wasm32-wasip1` runs as a guest command exactly like the bundled ones; link the\n`wasi-ext` bindings if it needs processes, users, or sockets, and leave them out\nfor a pure-compute tool. Heavy native binaries that are not yet available as\nWASM belong in a [mounted sandbox](/docs/sandbox) instead.\n\n## Recommendations\n\n- Use the bundled WASM coreutils and `sh` for normal shell workloads; they\n already carry the patched libc and the `wasi-ext` extensions.\n- To ship your own command, compile it for `wasm32-wasip1` with `cargo` (Rust)\n or the `wasi-sdk` `clang` (C), and link `wasi-ext` only if it needs the\n process / user / network host imports.\n- Keep the build and runtime contracts aligned: the patched `wasi-libc` and the\n `wasi-ext` import declarations a binary is compiled against are the same ones\n the [WASM VM](/docs/architecture/posix-syscalls) runtime expects to satisfy.","src/content/docs/docs/architecture/compiler-toolchain.mdx","49ef24cfa33c5d1f","docs/architecture/filesystem",{"id":355,"data":357,"body":359,"filePath":360,"digest":361,"deferredRender":16},{"title":111,"description":358,"skill":16},"Internals of the kernel VFS: the overlay/mount/root engines, how guest fs syscalls are routed and confined, WASM preopens, and mount confinement against symlink and .. escapes.","This page is an internals deep-dive on the **kernel virtual filesystem (VFS)**: how it is layered, how a guest `fs` syscall is routed through it, and how guest I/O is confined to the VM. For the user-facing API (reading, writing, mounting, persistence), see [Filesystem](/docs/filesystem).\n\nThe invariant this whole subsystem exists to uphold: **every guest filesystem operation is serviced by the kernel-owned VFS, never by a real host capability.** There is no host disk reachable from the guest. The VFS presents normal Linux semantics to tools while keeping every byte inside the kernel.\n\n\u003CNote>The security boundary is sidecar to executor. The VFS lives inside the trusted sidecar; the guest in the executor only ever *asks* for a filesystem operation. Confinement is the kernel's job, not the guest's. See the [Security Model](/docs/security-model) for the full threat model.\u003C/Note>\n\n## Where the VFS sits\n\nA guest `fs` call never touches the host. The path is always:\n\n```\nguest fs call (executor)\n -> kernel syscall (crosses sidecar \u003C-> executor boundary)\n -> VFS engine resolves the path\n -> backing store services the operation\n -> result returns to the executor\n```\n\n- The executor holds **no** filesystem capability of its own. It issues a syscall and blocks for the reply.\n- The kernel checks the applied permission policy for the filesystem scope before servicing the request.\n- The VFS resolves the path against the VM's layered engines, then services the operation against the engine that owns that path.\n\nBecause every byte is mediated here, two properties fall out for free: the guest can never reach the real host disk, and one VM's filesystem is never visible to another VM. Isolation is per-VM.\n\n## The VFS engines\n\nThe per-VM filesystem is not a single flat store. It is a tree of **engines**, each responsible for a subtree of the namespace. A path is resolved by walking from the root engine down to whichever engine owns the deepest matching prefix, then handing the remainder of the path to that engine.\n\n- **Root engine.** Owns `/` and the base namespace. Every VM boots with a root filesystem bootstrapped from a snapshot, so the guest starts against a populated POSIX tree (the default working directory is `/home/agentos`).\n- **Overlay engine.** Composes layers so writes land in a writable upper layer while reads fall through to a lower layer. This is how a read-mostly base can be presented as writable to the guest without mutating the shared lower layer.\n- **Mount engine.** Grafts a distinct backing store onto a guest path (a mount point). Below the mount point, operations are routed to that mount's backend instead of the parent engine. This is the mechanism behind in-memory, host-directory, S3, and Google Drive mounts.\n\nResolution is **longest-prefix wins**: if `/mnt/data` is a mount and the guest opens `/mnt/data/file`, the mount engine services it; anything outside `/mnt/data` stays with the parent (root/overlay) engine.\n\n```\n/ \u003C- root engine (bootstrapped from snapshot)\n|- home/user/... \u003C- root / overlay\n|- mnt/\n| |- scratch/... \u003C- mount engine -> in-memory backend\n| |- code/... \u003C- mount engine -> host-directory backend (read-only)\n| \\- data/... \u003C- mount engine -> S3 backend\n\\- ...\n```\n\nThe base layer is in-memory and per-VM; the runtime transparently persists it to backing storage so it survives sleep/wake. Mounts are pluggable: any guest path can be backed by the host, a remote, or a cloud store. See [Mounting filesystems](/docs/filesystem#mounts) for the user-facing config.\n\n## Routing a guest syscall\n\nWhen the guest calls, say, `readFileSync(\"/mnt/data/report.csv\")`:\n\n1. **Permission check.** The kernel verifies the filesystem scope is granted for that operation. Nothing is bound by default; access is denied until opted in (see [Permissions](/docs/permissions)).\n2. **Engine resolution.** The VFS walks the namespace and selects the engine owning the longest matching prefix (`/mnt/data` -> the S3 mount engine).\n3. **Path normalization and confinement.** The remainder of the path is normalized within the owning engine's root. `.` and `..` segments are resolved *before* the operation reaches the backend, so the request cannot climb above the engine's root.\n4. **Backend operation.** The owning engine's backend services the read/write/stat/etc. against its store (in-memory pages, the persisted base, a host directory, S3, ...).\n5. **Reply.** The result crosses back to the executor, which unblocks.\n\nHost-side APIs (`agent.writeFile`, `agent.readFile`) enter the *same* VFS from the trusted side, which is why the host can seed and read files the guest sees, without ever exposing the real host disk to the guest.\n\n## Mount confinement\n\nA host-backed mount (host directory, S3, ...) comes from trusted config, so its existence, target, and credentials are not attack surface. What *is* in scope is the guest-driven traffic through it: the guest must not be able to use a mounted path to reach bytes outside the mount root. Confinement is enforced by the kernel, on every operation:\n\n- **`..` traversal.** Path segments are normalized relative to the mount root before the backend sees them. A guest path like `/mnt/code/../../etc/passwd` cannot resolve above the mount root; it is clamped to the mount's own subtree (and, above the mount point, handed back to the parent engine, which is itself the kernel VFS, not the host).\n- **Symlinks.** Symlink resolution (`realpath` following) is performed by the kernel against the *virtual* namespace, not the host's. A symlink inside a host-directory mount cannot be used to escape the mount root onto the wider host filesystem; the resolved target is re-confined to the mount root.\n- **Path aliasing / TOCTOU.** Because resolution and confinement happen inside the kernel on each operation, there is no window where the guest resolves a path and the backend later acts on a different one. The guest sees only the mounted subtree, never the wider host filesystem.\n\nMounts for host and remote backends are **read-only by default**; a writable mount must be opted into explicitly. The `readOnly` flag is enforced at the engine, so a write syscall to a read-only mount fails inside the kernel rather than reaching the backend.\n\n\u003CNote>\"Trusted mount, untrusted traffic\": the mount's target is trusted configuration, but the guest drives I/O through it, so confining guest operations to the mount root (`..`, symlink, TOCTOU, path-aliasing) is squarely in scope and enforced by the kernel.\u003C/Note>\n\n## WASM preopens\n\nWASI does not grant a WASM guest an ambient filesystem. Instead, the host hands the module a set of **preopened directories**: capability handles to specific subtrees, and the guest can only reach paths reachable from a preopen.\n\nIn agentOS these preopens are wired to the **same kernel VFS** rather than to host directories:\n\n- A preopen maps a guest-visible path to a VFS subtree. File descriptors derived from it are serviced by the VFS engines above, with the same confinement rules.\n- The WASM guest therefore sees the virtualized filesystem (root snapshot, overlays, mounts) through standard WASI calls, with no host filesystem handle anywhere in the chain.\n- Confinement composes: a preopen rooted at a mount point inherits that mount's `..`/symlink confinement, because resolution still runs through the kernel VFS.\n\nThe result is that WASI filesystem access and the V8/Node `fs` path converge on one virtual filesystem, so both executor flavors get identical isolation and identical Linux semantics.\n\n## Where to go next\n\n- [Filesystem](/docs/filesystem): the user-facing API for reading, writing, mounting, and persistence.\n- [Architecture](/docs/architecture): the components, trust boundary, and kernel-owned syscall paths.\n- [Permissions](/docs/permissions): the filesystem scope the kernel checks on every operation.\n- [Security Model](/docs/security-model): the full trust model and threat boundary.","src/content/docs/docs/architecture/filesystem.mdx","6611b9713df06bfc","docs/architecture/limits-and-observability",{"id":362,"data":364,"body":367,"filePath":368,"digest":369,"deferredRender":16},{"title":365,"description":366,"skill":16},"Limits & Observability","How agentOS bounds resources, applies backpressure, warns before a limit is hit, and surfaces it all to the host.","agentOS runs untrusted, AI-generated code inside disposable VMs. Every resource\nthat code can consume is **bounded by default**, and every bound is designed to\n**warn before it is hit**, **fail with a clear error when it is**, and stay\n**inspectable** from one place. This page explains how the limits, backpressure,\nlogging, and observability pieces fit together across the stack.\n\n## Where limits live\n\nLimits are owned by **secure-exec** (the VM runtime) and **forwarded** by agentOS\n— the agentOS layer does not reimplement enforcement, it exposes the knobs and\nsurfaces the signals.\n\n| Layer | Responsibility |\n| --- | --- |\n| secure-exec kernel | Enforces per-VM resource caps (memory/heap, CPU time, fds, processes, sockets, filesystem bytes, …). |\n| secure-exec sidecar | Owns the bounded queues between the guest, the runtime, and the host; applies backpressure; tracks usage. |\n| agentOS client | Forwards `limits` config to the VM and surfaces limit signals to the caller. |\n\n## Three guarantees for every limit\n\nEvery bound — a resource cap, a bounded queue, a timeout, a payload size —\nfollows the same contract:\n\n1. **Bounded by default.** Nothing is unbounded out of the box. Memory is capped\n at ~128 MiB per isolate (Cloudflare Workers parity), CPU is bounded, and every\n queue has a fixed capacity. Operators may *raise* a cap, but never get an\n unbounded default.\n2. **Warn on approach.** As usage crosses a threshold (default **≥80%** of\n capacity), a structured warning is emitted — once per crossing, re-armed only\n after it drains back below 50% (hysteresis), so a busy limit logs once, not on\n every operation.\n3. **Clear, typed error on breach.** Exceeding a limit produces an error that\n names the limit, the observed-vs-cap value, and the config path to raise it —\n never a bare `EAGAIN`, a silent drop, or a crash.\n\n## Backpressure, not catastrophe\n\nThe path from guest code to the host is a **chain of bounded queues**: the V8\nruntime → a per-session frame channel → the V8→host event channel → the sidecar\nstdout frame queue → the host. When a queue fills (a slow host consumer, a chatty\ntool turn), the producer **blocks until the consumer drains a slot** — clean\nbackpressure that flows all the way back to the guest. A full queue never\ndestroys the session, silently drops data, or crashes the sidecar; a genuinely\ndead consumer surfaces as a typed terminal error instead.\n\nBuffer capacities are sized so that *transient* bursts are absorbed without ever\nengaging backpressure; backpressure is the safety net for a genuinely stuck\nconsumer, not a normal-operation event.\n\n## The limit registry\n\nAll bounded limits register with a single in-process **limit registry**. Each\nregistered limit tracks its live depth, high-water mark, and capacity, and emits\nthe near-capacity warning described above. This gives the runtime one place to\nanswer two questions:\n\n- *Is a limit about to be hit?* — the registry fires the approach warning.\n- *What is the current usage of everything?* — a registry snapshot lists every\n limit's depth / high-water / capacity / fill-percent for debugging.\n\nA CI audit fails the build if any limit-shaped constant is not classified and —\nfor operator-tunable ones — wired to a config field, so \"is everything bounded\nand config-wired?\" is verified mechanically rather than by review.\n\n## Logging & host visibility\n\nsecure-exec logs to **stderr** (never stdout — stdout is the framed wire\nprotocol). The default level is `WARN`, tunable with the `SECURE_EXEC_LOG`\nenvironment variable (`error` to quiet, `debug` for per-limit usage snapshots).\nNear-limit warnings and backpressure events therefore show up in the sidecar's\nstderr stream, which agentOS forwards to the host.\n\nThe limit registry also exposes a structured **warning sink**: a callback that\nfires on the same edge as the log, carrying `{ name, category, observed,\ncapacity, fillPercent }`. This is the foundation for host-facing limit\nobservability — a structured \"a limit is approaching capacity\" signal rather than\na parsed log line.\n\n## See also\n\n- [Resource Limits](/docs/resource-limits) — the full `limits` config surface.\n- [Processes](/docs/architecture/processes) and [Sessions & Persistence](/docs/architecture/sessions-persistence) — the layers the queue chain runs through.","src/content/docs/docs/architecture/limits-and-observability.mdx","da92f833b195bf95","docs/architecture/networking",{"id":370,"data":372,"body":375,"filePath":376,"digest":377,"deferredRender":16},{"title":373,"description":374,"skill":16},"Networking","How the kernel socket table works: a single VM-local transport that carries host, JavaScript, and WASM traffic, where fetch / net / dns route through it, how egress policy and loopback confinement are enforced, and how preview URLs are served.","This is the internals view of agentOS networking: the kernel socket table, the layers a request crosses, and where policy is enforced. For the user-facing API (`vmFetch`, preview URLs, the confinement model from a caller's perspective), see [Networking & Previews](/docs/networking). For the trust boundary this all sits inside, see [Architecture](/docs/architecture).\n\nThe governing rule is that there is exactly **one authoritative transport for everything VM-local**: the kernel socket table. No part of guest networking opens a real host socket on its own. Guest `fetch()`, `node:http`, `node:net`, WASM TCP clients and servers, and host-into-guest requests (`vmFetch` / `rt.fetch`) all target the same listener table.\n\n## The kernel socket table\n\nThe socket table is the floor of the stack and the only component that actually moves bytes between two in-VM endpoints. It is per VM, so two VMs never share a listener or a connection.\n\n- It exposes POSIX-style primitives: `socket_create`, `socket_bind_inet`, `socket_connect_inet_loopback`, `socket_read`, `socket_write`, `poll_targets`.\n- Every call is **owner-checked** (the calling process must own the descriptor) and **resource-accounted** against the VM's limits.\n- Failures return correct POSIX errnos (`ECONNREFUSED`, `EACCES`, …) so guest code branches the way it would on real Linux.\n- Connecting pairs two in-VM sockets and shuttles bytes between them. No host networking happens at this layer.\n\nBecause every server is a kernel TCP listener, a client never needs to know whether the server it is talking to is JS, WASM, raw TCP, or HTTP. HTTP is layered on top of kernel TCP bytes, so every listener lives in the one table and is reachable identically.\n\n\u003CNote>An earlier design carried two listener models at once: stream-mode listeners (`net.createServer`, WASM) on real kernel TCP sockets, and object-mode HTTP listeners (`http.createServer`) on a separate table that exchanged JSON request/response objects over stream events. A second guest process could not reach the object-mode table reliably, because the client expected byte-stream TCP semantics while the server only spoke object-mode dispatch. The current architecture removes the second model: everything is one socket table.\u003C/Note>\n\n## The four layers\n\nA request passes through four layers. Only the top and bottom understand HTTP; the middle two move bytes and enforce policy.\n\n| Layer | Role | Trust | Lives in |\n| --- | --- | --- | --- |\n| 4 · Guest bridge | `node:http` / `node:net` / `fetch` / undici shim | untrusted (V8 isolate) | `crates/execution/assets/v8-bridge.source.js` |\n| 3 · Sync-RPC dispatch | routes `net.connect`, `net.http_request`, `net.listen`, … | trusted | `crates/sidecar/src/service.rs` |\n| 2 · Execution & enforcement | listener state, host fetch client, permission checks | trusted (TCB) | `crates/sidecar/src/execution.rs` |\n| 1 · Kernel socket table | `bind` / `listen` / `connect` / `read` / `write`, loopback routing | trusted (TCB floor) | `crates/kernel/src/socket_table.rs`, `kernel.rs` |\n\n### Layer 1: kernel socket table\n\n`crates/kernel/src/kernel.rs` exposes the primitives above. Loopback routing is the heart of VM-local networking: `socket_connect_inet_loopback` only succeeds against a socket that is actually bound and listening in the same VM's table; otherwise it returns `ECONNREFUSED`. Resource-limit checks run before the two sockets are paired.\n\n### Layer 2: sidecar execution (enforcement point / TCB)\n\n`crates/sidecar/src/execution.rs` is where policy is applied. Two roles matter for networking:\n\n- **Listener state.** `build_javascript_socket_path_context` walks every active process and records what is listening on which port, including a map of HTTP loopback targets keyed by `(family, port)`. This is the source of truth a connect consults to learn that, say, \"port 3000 is an HTTP server owned by process X, server Y.\"\n- **Host fetch client.** When the host calls `vmFetch` / `rt.fetch()`, the sidecar resolves the target to a VM-owned kernel listener, opens its own kernel socket, connects over loopback, and speaks HTTP/1.1 to the guest server. This is the only HTTP client that lives in the sidecar (the host has no guest isolate to do framing for it).\n\n### Layer 3: sync-RPC dispatch\n\n`crates/sidecar/src/service.rs` routes the bridge calls guest code makes. The guest-to-guest loopback HTTP path lands here as `net.http_request`. It is the most security-sensitive RPC, so it is guarded in order:\n\n1. The host must be a loopback address.\n2. The applied network policy must permit the operation.\n3. The requested `(process_id, server_id)` must match a listener that is currently live.\n\nThat last check stops a guest from forging a target to reach a process it should not.\n\n### Layer 4: guest bridge\n\n`crates/execution/assets/v8-bridge.source.js` is the Node-compatibility shim inside the untrusted V8 isolate. It presents `node:http`, `node:net`, `fetch`, and undici to guest code and translates them into Layer 3 bridge calls. `http.createServer()` is implemented on top of `net.Server`: each accepted byte socket is parsed as HTTP and dispatched to the guest's request handler.\n\n## How fetch, net, and dns route through it\n\n- **`node:net` (raw TCP).** `net.connect` / `net.createServer` map directly onto kernel `connect` / `bind` + `listen`. The bytes are the payload; no framing is added.\n- **`node:http` and `fetch`.** A guest HTTP server is a `net.Server` whose accepted sockets are HTTP-parsed in the bridge. A guest HTTP client runs undici over a kernel-backed dispatcher (or a raw serializer for the loopback fast path). Either way the bytes travel as kernel TCP.\n- **DNS.** Name resolution is serviced by the kernel resolver, not the host. Outbound connections that leave the VM resolve through it, and the resolved addresses are then filtered by the egress allowlist (see below). DNS pinning ties the connection to the address that was checked, closing the resolve-then-reconnect TOCTOU gap.\n\n### Where HTTP meets TCP\n\nThere is no shared HTTP/TCP translation module. Because the wire between every endpoint is raw TCP bytes through the kernel, HTTP is framed and deframed **at each edge that speaks HTTP**. The kernel (Layer 1) and the sidecar routing (Layer 2) never parse HTTP. There are three independent codecs, one per kind of endpoint:\n\n| Endpoint | Lives in | Encode / decode |\n| --- | --- | --- |\n| Guest HTTP server | guest bridge | `parseLoopbackRequestBuffer` (bytes to object), `serializeLoopbackResponse` (object to bytes), wired per accepted socket by `attachHttpServerSocket` |\n| Guest HTTP client | guest bridge | undici over a kernel-backed dispatcher, or `serializeRawHttpRequest` + `waitForRawHttpResponse` |\n| Host fetch client | sidecar execution | `serialize_kernel_http_fetch_request` (request to bytes), `parse_kernel_http_fetch_response` (bytes to JSON) |\n\nA WASM HTTP server or client does its own framing in guest code (reading the request line, writing a response with standard C socket calls). The kernel does not help it; it is just bytes, the same as for the JS endpoints.\n\n## Data flows\n\n- **Host to guest (`vmFetch` / `rt.fetch`).** The sidecar resolves the port to a VM-owned kernel listener, opens a sidecar-owned kernel socket, connects over loopback, serializes the request bytes, drives the target process forward so it can accept and respond, then parses the response bytes back into the host response object. It is **fail-closed**: no DNS, no external networking, no host-loopback fallback. If no VM-owned listener exists, it returns a missing-listener error.\n- **Guest to guest.** `net.connect` goes through the sidecar, which returns a loopback HTTP target handle. The guest sends the request through `net.http_request`, which dispatches into the target process's request handler. Cross-process loopback passes through the enforcement point rather than taking an in-isolate shortcut.\n- **Cross-runtime (JS and WASM, either direction).** Client and server connect through a kernel loopback socket pair and exchange raw bytes. JS to WASM, WASM to JS, and WASM to WASM all use the same path; only the side that runs the HTTP codec differs.\n- **Guest outbound to host or external.** Connections that do not target a VM-owned listener take the external network path: permission checks, DNS pinning, then a real host `TcpStream`. Reaching a host loopback port still requires an explicit loopback exemption entry.\n\n## Egress policy and loopback confinement\n\nGuest networking is confined by three distinct controls plus the loopback-only default. The permission policy and limits are **trusted configuration**; the guest executor is the **untrusted subject** they bind.\n\n### Loopback-only by default\n\nGuest listeners are reachable only over loopback (`127.0.0.1` / `::1`) inside the VM.\n\n- Binding to `0.0.0.0` or `::` does not widen this: the kernel normalizes the unspecified address down to loopback, so the listener still answers only on loopback.\n- A connection that originates outside the loopback interface and targets a port the VM does not own is refused with `EACCES`, noting the port is not exempt.\n- This confinement is independent of the permission policy. Even with the network allowed, a guest server stays loopback-only unless its port is explicitly exempted.\n\n### Three stacked controls\n\nThese are often conflated but are separate. They stack, and a request must pass every one that applies:\n\n1. **Permission policy** (`network.listen` / `network.connect`). Decides whether the guest may open a listener or initiate an outbound connection at all. A blocked operation fails with `blocked by network.listen policy` or `blocked by network.connect policy`.\n2. **Loopback confinement.** Decides who may reach an already-permitted guest listener. By default only loopback inside the VM; a per-port exemption loosens it.\n3. **DNS / egress allowlist.** Constrains where permitted outbound connections may go. The kernel filters resolved addresses, blocking outbound access to restricted ranges, so an allowed `connect` can still be refused by destination.\n\nThe per-port loopback exemption belongs to layer 2 only. It is a trusted, per-port whitelist that *loosens* the default loopback confinement (for example, exposing an in-VM dev server beyond loopback). It is not an egress control and grants no outbound reach; layers 1 and 3 still apply. It is configured with `loopbackExemptPorts`, a list of ports that are exempt from the SSRF checks at layer 2; each listed port is reachable from outside the loopback interface, while the permission policy and egress allowlist continue to apply.\n\n### Trust and ownership\n\nEvery guest connect, listen, read, and write passes through sidecar ownership and kernel owner checks. Guest-to-guest loopback is allowed only when the destination is a VM-owned listener and the applied network policy permits the connect. Host-loopback access from guest code is separate and still requires a loopback exemption plus the applied network policy. Long-lived waits must not block the sync-RPC path, so the stack uses stream events, bounded polling, and kernel socket waits with explicit timeouts.\n\n\u003CNote>Host-to-guest requests bypass egress, not the table. `vmFetch` / `rt.fetch` terminate at the guest's loopback listener and never leave the VM, so they work even when guest egress (layer 3) or outbound `connect` (layer 1) is denied. They are host control-plane traffic, not guest egress, and only ever reach VM-owned listeners, while still going through the same kernel socket table as everything else.\u003C/Note>\n\n## Preview URLs\n\nA preview URL is port forwarding for a VM service: a time-limited, signed, publicly reachable URL that proxies HTTP to a port inside the VM. Mechanically it reuses the host-to-guest path:\n\n- A signed token is minted for a `(VM, port)` pair with an expiration, capped by `preview.maxExpiresInSeconds`. Tokens are stored in SQLite, survive sleep/wake cycles, and expired ones are cleaned up automatically.\n- An incoming request to the preview path is authenticated against the token, then proxied into the VM exactly like `vmFetch`: resolve the port to a VM-owned kernel listener, connect over loopback, frame HTTP/1.1, drive the target process, and stream the response back. The same fail-closed, VM-owned-listener-only rules apply.\n- CORS is enabled so browsers can reach preview URLs from any origin.\n- Revocation (`expireSignedPreviewUrl`) invalidates the token immediately, after which the proxy refuses the request before touching the socket table.\n\nBecause previews ride the host fetch path, they are subject to loopback confinement at the kernel but **not** to the guest egress allowlist: the request enters the listener from the host side and never becomes guest outbound traffic.\n\n## Where to go next\n\n- [Networking & Previews](/docs/networking): the `vmFetch` and preview URL API, with usage examples.\n- [Architecture](/docs/architecture): the client / sidecar / executor trust boundary this stack lives inside.\n- [Security Model](/docs/security-model): the full in-scope and out-of-scope threat model.","src/content/docs/docs/architecture/networking.mdx","9a3a667862c77ad4","docs/architecture/posix-syscalls",{"id":378,"data":380,"body":383,"filePath":384,"digest":385,"deferredRender":16},{"title":381,"description":382,"skill":16},"POSIX Syscalls","How agentOS extends WASI in two layers so WebAssembly guests behave like normal POSIX programs on top of the kernel.","Not everything inside an agentOS VM is JavaScript. The shell (`sh`) and the\ncoreutils behind [process execution](/docs/processes) ship as WebAssembly\nbinaries, and you can run your own WASM programs too. To make those programs\nbehave like normal Linux tools, agentOS presents a POSIX syscall surface on top\nof WebAssembly.\n\n- **WASM is a first-class guest.** WASM binaries run beside JavaScript inside the same VM.\n- **Same kernel, same boundary.** WASM syscalls route through the same kernel that backs JS guests, so there is no extra host access.\n- **POSIX shape, not host access.** The extensions below add process, user, and network *semantics*, all virtualized.\n\n## Why WASI alone is not enough\n\nThe base standard for WASM system access is **WASI** (specifically `wasip1`).\nWASI is intentionally minimal:\n\n- It gives a guest preopened file descriptors, clocks, randomness, and basic file I/O.\n- It has **no process model** (no `fork` / `exec` / `wait`).\n- It has **no users or groups** (no `getuid` / `getgid`).\n- It has **no general sockets** (no `connect` / `listen`).\n\nReal command-line programs expect all of those. agentOS closes the gap in two\nlayers, and both route through the kernel rather than the host.\n\n\u003CNote>\nEvery WASM syscall, like every JS syscall, goes through the kernel-owned virtual\nfilesystem, process table, and socket table. The extensions below add POSIX\n*shape*; they do not add host access. See the [Security Model](/docs/security-model)\nfor the isolation boundary.\n\u003C/Note>\n\n## The two-layer model\n\nagentOS layers a POSIX surface over WASM. Layer 1 adds capabilities WASI does\nnot express at all; Layer 2 adapts the standard WASI calls so a normal libc\nbehaves correctly inside the VM. Both bottom out in the kernel.\n\n\u003Csvg viewBox=\"0 0 700 360\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"Two-layer WASM-on-kernel model\" style=\"max-width: 700px; width: 100%; height: auto; font-family: ui-sans-serif, system-ui, sans-serif;\">\n \u003Crect x=\"0\" y=\"0\" width=\"700\" height=\"360\" fill=\"#ffffff\" />\n\n {/* Guest */}\n \u003Crect x=\"40\" y=\"20\" width=\"620\" height=\"56\" rx=\"8\" fill=\"#f4f4f5\" stroke=\"#d4d4d8\" />\n \u003Ctext x=\"350\" y=\"44\" text-anchor=\"middle\" font-size=\"15\" font-weight=\"600\" fill=\"#18181b\">WASM guest (sh, coreutils, your .wasm)\u003C/text>\n \u003Ctext x=\"350\" y=\"64\" text-anchor=\"middle\" font-size=\"12\" fill=\"#52525b\">compiled for wasm32-wasip1, linked against patched wasi-libc\u003C/text>\n\n {/* Layer 1 */}\n \u003Crect x=\"40\" y=\"100\" width=\"300\" height=\"120\" rx=\"8\" fill=\"#eef2ff\" stroke=\"#c7d2fe\" />\n \u003Ctext x=\"190\" y=\"124\" text-anchor=\"middle\" font-size=\"14\" font-weight=\"600\" fill=\"#3730a3\">Layer 1: host import modules\u003C/text>\n \u003Ctext x=\"190\" y=\"148\" text-anchor=\"middle\" font-size=\"12\" fill=\"#3730a3\">host_process — spawn / wait\u003C/text>\n \u003Ctext x=\"190\" y=\"168\" text-anchor=\"middle\" font-size=\"12\" fill=\"#3730a3\">host_user — uid / gid\u003C/text>\n \u003Ctext x=\"190\" y=\"188\" text-anchor=\"middle\" font-size=\"12\" fill=\"#3730a3\">host_net — TCP sockets\u003C/text>\n \u003Ctext x=\"190\" y=\"208\" text-anchor=\"middle\" font-size=\"12\" fill=\"#3730a3\">host_sleep_ms — blocking sleep\u003C/text>\n\n {/* Layer 2 */}\n \u003Crect x=\"360\" y=\"100\" width=\"300\" height=\"120\" rx=\"8\" fill=\"#ecfdf5\" stroke=\"#a7f3d0\" />\n \u003Ctext x=\"510\" y=\"124\" text-anchor=\"middle\" font-size=\"14\" font-weight=\"600\" fill=\"#065f46\">Layer 2: kernel-backed WASI shim\u003C/text>\n \u003Ctext x=\"510\" y=\"148\" text-anchor=\"middle\" font-size=\"12\" fill=\"#065f46\">stdio through the kernel bridge\u003C/text>\n \u003Ctext x=\"510\" y=\"168\" text-anchor=\"middle\" font-size=\"12\" fill=\"#065f46\">mounts mirrored as preopens\u003C/text>\n \u003Ctext x=\"510\" y=\"188\" text-anchor=\"middle\" font-size=\"12\" fill=\"#065f46\">read-only tiers enforced\u003C/text>\n \u003Ctext x=\"510\" y=\"208\" text-anchor=\"middle\" font-size=\"12\" fill=\"#065f46\">paths confined to their mount\u003C/text>\n\n {/* Arrows down to kernel */}\n \u003Cline x1=\"190\" y1=\"220\" x2=\"190\" y2=\"280\" stroke=\"#71717a\" stroke-width=\"2\" marker-end=\"url(#arrow)\" />\n \u003Cline x1=\"510\" y1=\"220\" x2=\"510\" y2=\"280\" stroke=\"#71717a\" stroke-width=\"2\" marker-end=\"url(#arrow)\" />\n\n {/* Kernel */}\n \u003Crect x=\"40\" y=\"284\" width=\"620\" height=\"56\" rx=\"8\" fill=\"#fafafa\" stroke=\"#d4d4d8\" />\n \u003Ctext x=\"350\" y=\"308\" text-anchor=\"middle\" font-size=\"15\" font-weight=\"600\" fill=\"#18181b\">Kernel: virtual filesystem, process table, socket table\u003C/text>\n \u003Ctext x=\"350\" y=\"328\" text-anchor=\"middle\" font-size=\"12\" fill=\"#52525b\">same paths that back JavaScript guests — no host escape\u003C/text>\n\n \u003Cdefs>\n \u003Cmarker id=\"arrow\" markerWidth=\"10\" markerHeight=\"10\" refX=\"6\" refY=\"3\" orient=\"auto\" markerUnits=\"strokeWidth\">\n \u003Cpath d=\"M0,0 L6,3 L0,6 Z\" fill=\"#71717a\" />\n \u003C/marker>\n \u003C/defs>\n\u003C/svg>\n\n## Layer 1: custom host import modules\n\nStandard WASI cannot express `fork` / `exec`, `getuid`, or `connect`. agentOS\ndeclares extra WebAssembly import modules that the host runtime implements, so\nguest libc can call them as if they were ordinary syscalls. These bindings live\nin the `wasi-ext` crate and cover three areas:\n\n- **`host_process`**: process management. Spawn a child process (argv, env, inherited stdio fds, working directory), wait for a child to exit, and related file-descriptor operations. This is what gives a WASM `sh` real [child process](/docs/processes) semantics; spawns go through the kernel process table.\n- **`host_user`**: user and group identity (uid, gid, user info). Base WASI has no concept of a user; this lets tools that call `getuid` / `getgid` see the VM's virtualized identity.\n- **`host_net`**: TCP sockets (connect, listen, send, receive) through the kernel socket table, gated by the same [network permission policy](/docs/networking) as everything else. Base WASI has no general socket API.\n\nA small `host_sleep_ms` binding provides blocking sleep. Together these let a\nguest compiled for `wasip1` behave as if it had a process model, user identity,\nand a network, all virtualized.\n\n```c\n// Imported from the host runtime, declared by the wasi-ext bindings.\n// Guest libc calls these as if they were ordinary syscalls.\n__attribute__((import_module(\"host_process\"), import_name(\"proc_spawn\")))\nint host_proc_spawn(const char *argv, const char *envp, int cwd_fd);\n\n// getuid returns an errno; the uid is written through the out-pointer.\n__attribute__((import_module(\"host_user\"), import_name(\"getuid\")))\nint host_getuid(unsigned int *ret_uid);\n\n__attribute__((import_module(\"host_net\"), import_name(\"net_connect\")))\nint host_net_connect(int fd, const char *addr, int addr_len);\n```\n\n## Layer 2: the kernel-backed WASI shim\n\nThe second layer adapts the standard WASI calls themselves so that programs\nbuilt against a normal libc behave correctly inside the VM. The embedded shim:\n\n- **Routes stdio through the kernel.** `fd_read` / `fd_write` on the standard descriptors go through the kernel stdio bridge rather than host file descriptors, so output stays inside the VM and honors PTYs and redirection.\n- **Fills in libc expectations.** For example `fcntl(F_SETFL)` is serviced via `fd_fdstat_set_flags`, so flag changes that libc performs do not fail.\n- **Mirrors mounts as preopens.** The preopen table reflects the VM's guest path mappings, so mounted directories are visible to WASM path resolution exactly as they are to JS and to `node:fs`.\n- **Enforces read-only tiers.** `path_open` rejects create / truncate / write flags on read-only mounts while still allowing non-mutating opens (directory traversal, `O_DIRECTORY`), so read-only mounts stay read-only without breaking `find`, `ls`, and friends.\n- **Confines paths to their mount.** Targets are resolved beneath the specific preopen's root, so `..` segments cannot escape one mount into a sibling mount or a host path.\n\n```\nfd_read(0) -> kernel stdio bridge (not a host fd)\nfcntl(fd, F_SETFL) -> fd_fdstat_set_flags (libc flag changes succeed)\npath_open(\"/data/x\") -> resolved under the /data preopen root\npath_open(..O_CREAT) -> rejected on a read-only mount\npath_open(\"../../etc\")-> stays inside the mount; cannot escape\n```","src/content/docs/docs/architecture/posix-syscalls.mdx","afdc817a0338ca3a","docs/architecture/processes",{"id":386,"data":388,"body":391,"filePath":392,"digest":393,"deferredRender":16},{"title":389,"description":390,"skill":16},"Processes","Internals of the kernel process model: the virtual process table, how spawns are serviced, stdio bridging, PTYs, and how WASM sh and coreutils map onto it.","This page is an internals deep-dive on the kernel's **process model**: the data\nstructures and syscall paths behind every guest process. For the client-facing\nAPI (`exec`, `spawn`, `openShell`, lifecycle, the process tree), see\n[Processes & Shell](/docs/processes). For the surrounding component and trust\nmodel, see [Architecture](/docs/architecture).\n\nTwo invariants frame everything below:\n\n- **No real host process is ever spawned for guest work.** Every guest process is an entry in a kernel-owned virtual process table, not an OS process. Guest JavaScript runs in V8 isolates; guest commands like `sh` and coreutils run as WebAssembly. Neither is `node` or a host binary.\n- **Every process operation is a syscall into the kernel.** Spawning, waiting, signaling, reading stdout, and resizing a PTY all cross from the untrusted executor into the sidecar-owned kernel, which services them against virtualized resources.\n\n## The virtual process table\n\nEach VM owns one process table. It is the authority for what is \"running\"\ninside that VM; nothing in it corresponds to a host PID.\n\n- **Per-VM and isolated.** Two VMs have two independent tables. A PID in one VM is meaningless in another, and processes are never visible across the VM boundary.\n- **Holds every guest process,** not only the ones a client started explicitly. A `spawn` from the client, a child spawned by guest `node:child_process`, and the processes behind a shell pipeline are all table entries. This is why the system-wide views (`allProcesses`, `processTree`) can show more than what the client launched.\n- **Tracks lifecycle and lineage.** Each entry carries its PID, the command and arguments, parent PID (so the tree can be reconstructed), running/exited status, exit code once collected, and its attached stdio endpoints.\n- **Records a driver.** An entry knows which execution backend services it (for example a V8 isolate versus a WASM runtime). This is the `driver` field surfaced on `allProcesses`. Drivers differ in *how* the code runs; they share the same table, the same kernel-owned stdio, and the same boundary.\n\n\u003CNote>The process table is part of the kernel the sidecar owns. The executor never mutates it directly; it can only ask the kernel to create, wait on, or signal an entry. That request-only relationship is the sidecar-to-executor boundary applied to processes.\u003C/Note>\n\n## How a spawn is serviced\n\nA spawn, whether it originates from a client `spawn`/`exec` call or from guest\n`node:child_process`, follows one path through the kernel:\n\n1. **The request crosses into the kernel.** A client call arrives over the wire protocol; a guest call arrives as a syscall from the executor. Either way the kernel, not the caller, performs the work.\n2. **Permission check.** The kernel applies the VM's permission policy before doing anything. Process execution is denied by default and must be granted; the policy is trusted input, the guest making the request is not.\n3. **Resolve the program.** The command is resolved against the VM's virtual filesystem (PATH lookup over the VFS), not the host. The resolved program decides the driver: a JavaScript entrypoint runs in a V8 isolate; a `.wasm` program (including `sh` and coreutils) runs on the WASM runtime.\n4. **Allocate the table entry.** The kernel assigns a virtual PID, records the command, arguments, environment, working directory, and parent PID, and links stdio endpoints (see below).\n5. **Start execution.** The driver begins running the program. For a one-shot `exec` the kernel additionally collects stdout, stderr, and the exit code and returns them as the call's result; for `spawn` it leaves the process running and streams output via events.\n6. **Reap and record exit.** When the program finishes, the kernel records the exit code on the table entry and marks it exited, which is what a `wait`/`waitProcess` resolves against and what `processExit` reports.\n\nSignals (`stopProcess` / SIGTERM, `killProcess` / SIGKILL) are the same shape: a\nrequest into the kernel, which applies it to the virtualized process rather than\nto any host process.\n\n## Stdio bridging\n\nStandard streams are kernel-owned objects, not host file descriptors. Each\nprocess entry has stdin, stdout, and stderr endpoints that the kernel wires up\nwhen the entry is created.\n\n- **Capture vs. stream.** For `exec`, the kernel buffers stdout and stderr and hands them back when the process exits. For `spawn`, output is delivered incrementally as `processOutput` events tagged with the PID and the stream (`stdout`/`stderr`), and `processExit` signals completion.\n- **Writable stdin.** `writeProcessStdin` pushes bytes into the process's stdin endpoint; `closeProcessStdin` closes the write side so programs that read to EOF (like `cat`) can finish. None of this touches a real pipe on the host.\n- **Pipes between processes.** Shell pipelines (`a | b`) connect one process's stdout endpoint to the next process's stdin endpoint through kernel-owned pipes. The pipe is a virtual object in the kernel, so a pipeline behaves like Linux without any host IPC.\n\nBecause these endpoints are kernel objects, the same bridging works identically\nwhether the process is a V8 isolate or a WASM program; the driver writes to and\nreads from kernel stdio, not from anything host-provided.\n\n## PTYs and interactive shells\n\nAn interactive shell needs a terminal, not just piped stdio: line editing, job\ncontrol signals, and window size all depend on a PTY. The kernel provides\nvirtual PTY devices for this.\n\n- **A shell is a process plus a PTY.** `openShell` allocates a kernel PTY and starts a shell process attached to it, returning a `shellId`. The PTY is a virtualized terminal device, never a host `/dev/pts` entry.\n- **Bidirectional terminal I/O.** `writeShell` feeds keystrokes into the PTY master side; everything the shell and its children emit comes back as `shellData` events. This carries terminal control sequences, so full-screen TUIs behave correctly.\n- **Resize is a terminal operation.** `resizeShell` updates the PTY's window size (columns and rows), which the kernel propagates to the foreground process the way a real terminal resize would, so programs relying on `TIOCGWINSZ`-style sizing redraw correctly.\n- **Teardown.** `closeShell` tears down the PTY and the attached shell process. An open shell keeps the VM active, the same way an open PTY keeps a session alive on a real system.\n\n## WASM sh and coreutils on the process model\n\nThe shell and the standard commands behind process execution are not special\nhost helpers; they are ordinary guest processes that happen to be WebAssembly.\nFor the full WASM execution model see [WASM VM](/docs/architecture/posix-syscalls); here is how it\nmaps onto the process table specifically.\n\n- **They are normal table entries.** Running `sh`, `ls`, `cat`, etc. allocates virtual PIDs and table entries exactly like any other process, with the WASM driver recorded on each. A pipeline of coreutils is several entries linked by kernel pipes.\n- **POSIX process semantics are virtualized, not borrowed from the host.** Plain WASI has no process model (no `fork`/`exec`/`wait`). agentOS supplies those semantics through kernel-backed host imports, so a WASM program that spawns and waits on a child drives the *same* kernel process table that JS guests use. A coreutil spawning a subcommand is one table entry creating another.\n- **Same stdio, same PTY.** WASM processes read and write the kernel stdio endpoints described above, and a shell built from WASM `sh` attaches to a kernel PTY just like any interactive shell. The driver differs; the kernel-owned plumbing does not.\n\nThis is why the process model is uniform: whether an entry is a V8 isolate or a\nWASM binary, it lives in the same per-VM table, goes through the same\npermission-checked spawn path, and uses the same kernel-owned stdio and PTYs.\n\n## See also\n\n- [Processes & Shell](/docs/processes): the client API for running and managing processes.\n- [WASM VM](/docs/architecture/posix-syscalls): how WebAssembly guests get POSIX process, user, and network semantics.\n- [Architecture](/docs/architecture): components, the trust boundary, and the request lifecycle.\n- [Permissions](/docs/permissions): the policy the kernel checks on every spawn.","src/content/docs/docs/architecture/processes.mdx","000c86cdd9459fa0","docs/architecture/sessions-persistence",{"id":394,"data":396,"body":399,"filePath":400,"digest":401,"deferredRender":16},{"title":397,"description":398},"Sessions & Persistence","How agentOS, ACP, RivetKit actors, and durable session persistence fit together.","agentOS runs coding agents inside VMs and talks to them through the Agent\nCommunication Protocol (ACP). RivetKit wraps those VMs in durable actors, so a\nsession can survive actor sleep/wake even though the live VM and agent process\ndo not.\n\n## Layers\n\nagentOS session architecture has four layers:\n\n| Layer | Responsibility |\n| --- | --- |\n| RivetKit actor | Owns the public API and durable actor-local SQLite state. |\n| agentOS client | Thin facade used by the actor to create sessions, prompt agents, and call the sidecar. |\n| agentOS sidecar ACP extension | Launches ACP adapters inside the VM, speaks JSON-RPC, handles permissions, and owns resume orchestration. |\n| ACP adapter / agent | Runs inside the VM and speaks ACP over stdio. |\n\nThe actor is durable. The VM is disposable. The ACP agent process is live state\ninside the VM.\n\n## API Shape\n\nThe actor-facing session API is:\n\n- `createSession(agentType, options)`\n- `sendPrompt(sessionId, text)`\n- `closeSession(sessionId)`\n- `listPersistedSessions()`\n- `getSessionEvents(sessionId)`\n\n`sessionId` is the stable, client-facing id. If fallback resume creates a new\nlive ACP session id after wake, the actor keeps an internal\n`externalSessionId -> liveSessionId` remap. Clients keep using the original\n`sessionId`.\n\n## Create Flow\n\n1. The actor calls agentOS `createSession`.\n2. The sidecar starts the ACP adapter process inside the VM.\n3. The sidecar sends ACP `initialize`.\n4. The sidecar sends ACP `session/new`.\n5. The actor persists session metadata in `agent_os_sessions`.\n6. The actor starts capturing ACP `session/update` events for the session.\n\nPersisted session metadata includes:\n\n- `session_id`\n- `agent_type`\n- agent capabilities and agent info\n- create-time `cwd`\n- create-time `env`\n\nThe create-time `cwd` and `env` are used later so resumed sessions start with\nthe same working directory and environment they were created with.\n\n## Prompt Flow\n\n1. The actor receives `sendPrompt(sessionId, text)`.\n2. If the session is persisted but not live in the current VM, the actor lazily\n resumes it first.\n3. The actor writes a synthetic `user_prompt` event before forwarding the\n prompt.\n4. The actor forwards the prompt to the live ACP session id.\n5. The sidecar sends ACP `session/prompt`.\n6. Inbound ACP `session/update` events are captured into\n `agent_os_session_events`.\n\n`agent_os_session_events` is ordered per session. Sequence numbers are allocated\ninside the SQLite insert so concurrent prompt and stream captures cannot reuse\nthe same sequence number.\n\n## Sleep And Wake\n\nWhen a RivetKit actor sleeps:\n\n- the VM is destroyed\n- ACP adapter processes exit\n- the actor's in-memory `live_sessions` remap is lost\n- actor SQLite survives\n\nWhen the actor wakes:\n\n- a fresh VM boots\n- stable session ids still exist in `agent_os_sessions`\n- no ACP session is live yet\n- resume happens lazily on the next prompt\n\n## Resume Flow\n\nOn the first post-wake prompt for a persisted session:\n\n1. The actor reads `agent_os_sessions`.\n2. The actor reconstructs a Markdown transcript from\n `agent_os_session_events`.\n3. The actor writes the transcript to\n `/root/.agentos/threads/\u003CsessionId>.md`.\n4. The actor calls sidecar `resumeSession` with:\n - stable external `sessionId`\n - agent type\n - transcript path\n - persisted create-time `cwd`\n - persisted create-time `env`\n\nThe sidecar then chooses one of two resume paths.\n\n### Native Resume\n\nIf the ACP agent advertises `loadSession` or `resume`, the sidecar sends\n`session/load` or `session/resume`.\n\nWhen native resume succeeds:\n\n- the live ACP id is the stable external `sessionId`\n- the agent restores its own context\n- no transcript preamble is injected\n\nOpenCode uses this path when its own session store is still available in the\ndurable VM filesystem.\n\n### Transcript Fallback\n\nIf native resume is unsupported, or if native resume reports a normalized\n`unknown_session`, the sidecar falls back to a fresh session:\n\n1. The sidecar sends ACP `session/new`.\n2. The sidecar returns the new live ACP id to the actor.\n3. The actor stores `externalSessionId -> liveSessionId`.\n4. The sidecar prepends a one-shot preamble to the next prompt pointing at the\n transcript path.\n\nThe fallback is universal because it only requires the agent to read a file with\nits normal tools. It is lower fidelity than native resume because the transcript\nis pointed to, not automatically loaded into the agent's context window.\n\n## Unknown Session Normalization\n\nAdapters report missing sessions differently. The sidecar normalizes known\nmissing-session shapes into:\n\n```json\n{ \"error\": { \"data\": { \"kind\": \"unknown_session\" } } }\n```\n\nFor example, OpenCode currently reports a missing native session as:\n\n```json\n{ \"code\": -32603, \"data\": { \"details\": \"NotFoundError\" } }\n```\n\nThat shape is captured before normalization in tests, then normalized so the\nresume state machine can safely choose transcript fallback. Other internal\nerrors still propagate as failures.\n\n## Persistence\n\nDurable session state lives in actor SQLite:\n\n| Table | Purpose |\n| --- | --- |\n| `agent_os_sessions` | Stable session registry, agent type, capabilities, agent info, create-time `cwd`, and create-time `env`. |\n| `agent_os_session_events` | Append-only prompt and ACP event log keyed by the stable external `sessionId`. |\n\nThe transcript file is not canonical state. It is a disposable render of\n`agent_os_session_events`, rebuilt on demand during fallback resume.\n\n## What Is Durable\n\n| Data | Survives sleep/wake? | Notes |\n| --- | --- | --- |\n| Actor SQLite | Yes | Stores session registry, events, preview tokens, and other actor data. |\n| VM filesystem | Yes, when backed by the actor sqlite_vfs root | Used by agents and resume transcripts. |\n| Live ACP process | No | Recreated on wake. |\n| Actor in-memory vars | No | Includes the live ACP id remap. |\n| Client-facing `sessionId` | Yes | Stored in `agent_os_sessions`. |\n\n## Where To Look In Code\n\n- Sidecar ACP orchestration:\n `crates/agentos-sidecar/src/acp_extension.rs`\n- agentOS TypeScript client surface:\n `packages/core/src/agent-os.ts`\n- RivetKit actor session actions:\n `rivetkit-rust/packages/rivetkit-agent-os/src/actions/session.rs`\n- RivetKit persistence helpers:\n `rivetkit-rust/packages/rivetkit-agent-os/src/persistence.rs`","src/content/docs/docs/architecture/sessions-persistence.mdx","7a4569a76c301276","docs/custom-software/building-wasm",{"id":402,"data":404,"body":407,"filePath":408,"digest":409,"deferredRender":16},{"title":405,"description":406},"Building Binaries","Compile WASM command binaries for agentOS from source in the secure-exec registry.","WASM command packages ship **compiled `.wasm` binaries** in their `bin/` that run inside the VM as guest commands. The binaries are build artifacts and are not checked into git, so to add or change a command you build it from source in the **secure-exec registry**.\n\n\u003CNote>\nYou only need this to author new commands. To use existing ones, install the published package (e.g. `@agentos-software/ripgrep`) and pass it to `software`. See [using the registry](#using-the-registry) below.\n\u003C/Note>\n\n## Where it lives\n\nCommand source and packages live under `registry/` in [secure-exec](https://github.com/rivet-dev/secure-exec/tree/main/registry):\n\n- **`registry/native/crates/commands/\u003Cname>/`**: the Rust source for each command — a cargo package named `cmd-\u003Cname>` that emits a `\u003Cname>` binary.\n- **`registry/native/c/`**: the C source for the C-built commands.\n- **`registry/software/\u003Cname>/`**: the npm package for each command set (`@agentos-software/\u003Cname>`). It exports a `{ packageDir }` descriptor pointing at the self-contained runtime directory assembled at `dist/package/`, and declares which binaries it ships in its `agentos-package.json` (`commands`, plus optional `aliases` and `stubs`).\n\n## Build\n\nEverything runs through `just` recipes at the secure-exec repo root:\n\n```bash\njust registry-native # compile ALL native wasm binaries (slow; once per checkout)\njust registry-native-cmd sh # recompile ONE command (cargo package cmd-sh)\njust registry-build # stage + assemble every registry package\njust registry-build ripgrep # ... or just one\njust registry-status # per-package state; --remote adds npm dist-tags\n```\n\nThe native build compiles each command for `wasm32-wasip1` with the pinned **nightly** toolchain from `rust-toolchain.toml` (the build vendors and patches `std` for WASI), optimizes with `wasm-opt`, and drops the binaries in `registry/native/target/wasm32-wasip1/release/commands/`. C-based commands (e.g. `sqlite3`, `unzip`, `wget`, `zip`) compile with a **wasi-sdk** clang toolchain via `make -C registry/native/c`.\n\nEach package's build then runs the **agentos-toolchain** lifecycle: `agentos-toolchain stage` copies the binaries listed in the package's `agentos-package.json` into its `bin/`, and `agentos-toolchain build` assembles the clean `dist/package/` runtime dir (the `{ packageDir }` target) with a `bin` map in its `package.json`.\n\n## Add a new command package\n\n1. Add the command source as `registry/native/crates/commands/\u003Cname>/` (cargo package `cmd-\u003Cname>`; Rust) or under `registry/native/c/` (C).\n2. Create `registry/software/\u003Cname>/` as an `@agentos-software/\u003Cname>` npm package that exports a `{ packageDir }` descriptor pointing at `dist/package/`.\n3. Declare the shipped binaries in its `agentos-package.json`: `{ \"commands\": [\"\u003Cname>\"] }` (plus `aliases`/`stubs` if needed).\n4. If it belongs in a meta-package (e.g. `common` or `build-essential`), add it there.\n5. Verify with `just registry-native-cmd \u003Cname> && just registry-build \u003Cname>` and `just registry-test`.\n\n## Let an agent build it\n\nThis is a mechanical, well-scoped task, so you can hand it to a coding agent. A prompt like:\n\n```text\nAdd a WASM command package for `\u003Ccommand>` to the secure-exec registry:\n- put the Rust source at registry/native/crates/commands/\u003Ccommand>/ as a cargo\n package named cmd-\u003Ccommand>,\n- create registry/software/\u003Ccommand>/ as an @agentos-software/\u003Ccommand> npm\n package that exports a { packageDir } descriptor and declares the command in\n its agentos-package.json,\nthen run `just registry-native-cmd \u003Ccommand> && just registry-build \u003Ccommand>`\nand `just registry-test`, and fix any failures.\n```\n\n## Using the registry\n\nInstall a published package and pass it to `software`. Registry WASM packages are `{ packageDir }` descriptors — import and pass them directly:\n\n\u003CCodeSnippet file=\"examples/software/registry-usage.ts\" />\n\nMeta-packages bundle a full set, e.g. `@agentos-software/common` (coreutils, sed, grep, gawk, findutils, diffutils, tar, gzip). Run the commands from the client; see [Processes & Shell](/docs/processes). Browse the full catalog on the [Registry](/registry), and see the package descriptor in [Software Definition](/docs/custom-software/definition). To ship your package to npm or use a local build, see [Publishing Packages](/docs/custom-software/publishing).","src/content/docs/docs/custom-software/building-wasm.mdx","308dd6a240282f91","docs/custom-software/definition",{"id":410,"data":412,"body":415,"filePath":416,"digest":417,"deferredRender":16},{"title":413,"description":414},"Software Definition","The software-package definition for custom commands and agents in an agentOS VM: a package is a directory, declared with defineSoftware({ packageDir }).","**Software** is anything you install into a VM — **commands** (executables in a package's `bin/`) or an **agent** (a package that also exposes an ACP session).\n\nA package is a **self-contained directory**: package it first, then point `defineSoftware()` at it with `{ packageDir }`. The package's name, optional agent block, and any files/env it provides all live in an `agentos-package.json` at the root of that directory — the sidecar reads it when it mounts the package, so the client only forwards the directory. Pick the quickstart that matches what you're packaging.\n\n## Quickstart\n\n### WebAssembly\n\n\u003CSteps>\n\n1. **You have** C or Rust source for a command. (Most common commands already ship as `@agentos-software/*` packages you can use directly — compile only new or custom ones.)\n\n2. **Compile it** to WebAssembly — see [Building Binaries](/docs/custom-software/building-wasm). There's **no `pack` step**: WASM binaries are self-contained, so the compile output is already the package — a `bin/` of `\\0asm` files plus a `package.json` for the name/version:\n\n ```\n my-cmds/\n ├── package.json\n └── bin/\n ├── tool-a # \\0asm WebAssembly\n └── tool-b\n ```\n\n3. **Define it** — point `defineSoftware()` at that directory:\n\n \u003CCodeSnippet file=\"examples/software/quickstart-wasm/my-cmds.ts\" />\n\n4. **Use it** — pass it to a VM; the commands are on `$PATH`:\n\n \u003CCodeSnippet file=\"examples/software/quickstart-wasm/index.ts\" />\n\n\u003C/Steps>\n\n### Node.js\n\n\u003CSteps>\n\n1. **You have** a local project whose `package.json` `bin` names its commands:\n\n ```\n my-tool/\n ├── package.json # \"bin\": { \"my-tool\": \"cli.js\" }\n └── cli.js # #!/usr/bin/env node\n ```\n\n2. **Package it** — `pack` installs the full dependency closure into a self-contained package directory (a flat `node_modules` plus a `bin` map of real files):\n\n ```bash\n npx @rivet-dev/agentos-toolchain pack ./my-tool\n # writes ./my-tool-package/ (override the location with --out \u003Cdir>)\n # my-tool-package/\n # ├── package.json # \"bin\": { \"my-tool\": \"node_modules/my-tool/cli.js\" }\n # └── node_modules/ # flat, self-contained closure\n ```\n\n Commands come from the package's `package.json` `bin` map — **real files, no symlinks** — so the result ships cleanly as an npm dependency. (The runtime makes the `/opt/agentos/bin` symlinks itself when it mounts the package.) A native `.node` addon is an error (it can't run in V8); re-run with `--prune-native` to drop unreachable ones.\n\n3. **Define it** — point `defineSoftware()` at the packaged directory:\n\n \u003CCodeSnippet file=\"examples/software/quickstart-node/my-tool.ts\" />\n\n4. **Use it** — pass it to a VM; `my-tool` is now on `$PATH`:\n\n \u003CCodeSnippet file=\"examples/software/quickstart-node/index.ts\" />\n\n\u003C/Steps>\n\n### Agent\n\nAn agent is a Node.js or WASM package (packaged exactly as above) whose `agentos-package.json` carries an **`agent` block** naming a `bin/` command that speaks ACP over stdio.\n\n\u003CSteps>\n\n1. **You have** an npm package with a `bin/` command that speaks ACP over stdio.\n\n2. **Package it** — same `pack` as Node.js, with `--agent` naming the ACP entrypoint. That writes the `agent` block into the package's `agentos-package.json`:\n\n ```bash\n npx @rivet-dev/agentos-toolchain pack @scope/my-agent --out ./packages --agent my-agent-acp\n # → ./packages/my-agent/current (its agentos-package.json now has the agent block)\n ```\n\n3. **Define it** — point `defineSoftware()` at the packaged directory; the agent block is already in its `agentos-package.json`:\n\n \u003CCodeSnippet file=\"examples/software/quickstart-agent/my-agent.ts\" />\n\n4. **Use it** — `createSession()` launches the agent by spawning its `acpEntrypoint`:\n\n \u003CCodeSnippet file=\"examples/software/quickstart-agent/index.ts\" />\n\n\u003C/Steps>\n\n## Reference\n\n### The descriptor\n\nA software entry is just a pointer to the packaged directory:\n\n```ts\ndefineSoftware({\n packageDir: string, // absolute host path to the self-contained package directory\n})\n```\n\n`packageDir` must contain **only the package** — a `package.json` with a `bin` map, the runtime\nfiles (`bin/`, a flat `node_modules`), and an `agentos-package.json`. It is mounted read-only, so\n**don't point it at a source root**: that drags `src/`, dev `node_modules/`, `tsconfig`, and build\ncaches into the VM. Point it at a clean build output — `pack` and the WASM assemble step both emit\none.\n\n\u003CNote>\n`pack` already emits a clean directory. For a package you build by hand (e.g. compiled WASM),\nassemble a `dist/package/` holding just `package.json` + `bin/` and point `packageDir` there —\nnever at the workspace root:\n\n```ts\n// dist/package/ ← { package.json (name, version, bin), bin/\u003Ccmd>…, agentos-package.json }\nconst packageDir = resolve(import.meta.dirname, \"dist/package\");\nexport default defineSoftware({ packageDir });\n```\n\u003C/Note>\n\n### `agentos-package.json`\n\nThe package's name, optional agent block, and any files/env it provides live in an\n`agentos-package.json` at the root of `packageDir`. The sidecar reads it when it mounts the\npackage, so this metadata never travels on the wire. For command/WASM packages it is **generated**\nfor you (name from `package.json`); for agents you author the `agent` block (or\n`agentos-toolchain pack --agent \u003Ccmd>` writes it).\n\n```jsonc\n{\n \"name\": \"my-agent\", // → /opt/agentos/\u003Cname>\n \"agent\": { // optional — also exposes an agent session\n \"acpEntrypoint\": \"my-agent-acp\", // bin/ command that speaks ACP over stdio\n \"env\": { }, // static env for the adapter\n \"launchArgs\": [],\n \"snapshot\": false // SDK snapshot optimization\n },\n \"provides\": { // optional — files + env the package contributes\n \"env\": { \"EXAMPLE_HOME\": \"/opt/agentos/my-agent\" },\n \"files\": [{ \"source\": \"etc/example.conf\", \"target\": \"/etc/example.conf\" }]\n }\n}\n```\n\n- **`name`** — the package name; commands and the package mount under `/opt/agentos/\u003Cname>`.\n- **`agent.acpEntrypoint`** — the `bin/` command spawned to start a session; speaks ACP over stdio.\n- **`agent.env`** — static env vars for the adapter, merged under the user env. Every command is on `$PATH`, so point at one directly, e.g. `{ \"PI_ACP_PI_COMMAND\": \"/opt/agentos/bin/pi\" }` so `pi-acp` can spawn the `pi` CLI.\n- **`agent.launchArgs`** — extra CLI args prepended when launching the adapter.\n- **`agent.snapshot`** (default `false`) — load the SDK [once per sidecar](#sdk-snapshotting--snapshot-safety) via a shared V8 heap snapshot instead of per session. Falls back to per-session loading if the SDK isn't snapshot-safe, so it only affects startup latency.\n- **`provides.env`** — env vars merged into the VM's base environment (existing values win — a package never clobbers the user env).\n- **`provides.files`** — read-only files overlaid into the VM filesystem. Each `{ source, target }` maps a path **inside the package** to an **absolute VM path**; the sidecar mounts them as zero-copy read-only lower layers (a guest write copies-up, never touching the host). A missing `source` is a fatal packaging error.\n\n## Advanced\n\n### Meta-packages\n\nA software entry may be an **array** of descriptors, so one package can bundle several. Pass arrays directly to `software`:\n\n```ts\nconst vm = agentOS({\n software: [pi, buildEssential /* = [coreutils, make, git, curl] */],\n});\n```\n\n### SDK snapshotting & snapshot-safety\n\nA V8 heap snapshot freezes the heap *after* the SDK's modules are evaluated, then seeds each new session's isolate from it. This works only if the SDK's **module-init** code (everything that runs at `import`/`require` time) doesn't:\n\n- Create **native handles** — load a `.node` addon, instantiate WebAssembly, or produce a V8 `External`/`Foreign` at top level.\n- Open a **file descriptor, socket, timer, or worker**, or leave a **pending promise**.\n- Bake in **non-deterministic or per-session state** — `process.env`, cwd, `Date.now()`, `Math.random()`, a UUID.\n\nDefer all of the above behind functions or lazy `import()` that run per session. Leave `agent.snapshot: false` for any SDK that can't — the agent still runs, just without the speedup.\n\n## Next steps\n\n- [Custom Agents](/docs/agents/custom): the agent-focused guide.\n- [Building Binaries](/docs/custom-software/building-wasm): compile WASM commands and use the registry.\n- [Packages & command resolution](/docs/architecture/packages-and-command-resolution): how packages mount and resolve.\n- [Request Software](https://github.com/rivet-dev/agentos/issues/new/choose): ask for a package you need.","src/content/docs/docs/custom-software/definition.mdx","dcd2bb664473b0e4","cookbooks/agent-to-agent",{"id":418,"data":420,"body":423,"digest":424,"rendered":425},{"title":421,"description":422},"Agent to Agent","Bridge two isolated agent VMs: a writer agent calls a reviewer agent through a binding.","\nRun two agents in separate isolated VMs and let one delegate to the other. The writer agent produces code, then hands it to a reviewer agent for feedback — without the two VMs ever sharing a filesystem. Reach for this when you want specialized agents that collaborate but stay isolated.\n\n## How it works\n\nBoth agents are independent `agentOS` VMs registered under one `setup`. The writer is given a `review` binding: a host-side tool the agent can invoke by name. When the writer runs `agentos-review submit --path ...`, the binding's `execute` runs on the host, where it reads the file out of the writer's VM, copies it into the reviewer's VM, opens a reviewer session, and prompts the reviewer to review the code. The review text is returned to the writer as the binding's result. The two VMs never touch directly — the host bridge is the only path between them.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start both agent VMs\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # drive the writer, which calls the reviewer\n```\n\nThe writer writes an API, submits it through the binding, and the reviewer's feedback comes back inline.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/agent-to-agent)\n","a205e7ced7207aa0",{"html":426,"metadata":427},"\u003Cp>Run two agents in separate isolated VMs and let one delegate to the other. The writer agent produces code, then hands it to a reviewer agent for feedback — without the two VMs ever sharing a filesystem. Reach for this when you want specialized agents that collaborate but stay isolated.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Both agents are independent \u003Ccode>agentOS\u003C/code> VMs registered under one \u003Ccode>setup\u003C/code>. The writer is given a \u003Ccode>review\u003C/code> binding: a host-side tool the agent can invoke by name. When the writer runs \u003Ccode>agentos-review submit --path ...\u003C/code>, the binding’s \u003Ccode>execute\u003C/code> runs on the host, where it reads the file out of the writer’s VM, copies it into the reviewer’s VM, opens a reviewer session, and prompts the reviewer to review the code. The review text is returned to the writer as the binding’s result. The two VMs never touch directly — the host bridge is the only path between them.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start both agent VMs\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # drive the writer, which calls the reviewer\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start both agent VMs\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # drive the writer, which calls the reviewer\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start both agent VMs\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # drive the writer, which calls the reviewer\n\u003C/code>\u003C/pre>\n\u003Cp>The writer writes an API, submits it through the binding, and the reviewer’s feedback comes back inline.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/agent-to-agent\">View source on GitHub\u003C/a>\u003C/p>",{"headings":428,"localImagePaths":439,"remoteImagePaths":440,"frontmatter":441},[429,433,436],{"depth":430,"slug":431,"text":432},2,"how-it-works","How it works",{"depth":430,"slug":434,"text":435},"run-it","Run it",{"depth":430,"slug":437,"text":438},"source","Source",[],[],{},"cookbooks/approvals",{"id":442,"data":444,"body":446,"digest":447,"rendered":448},{"title":23,"description":445},"Handle live permission requests with auto-approve and selective-approval flows.","\nWhen an agent wants to read a file, write output, or run a command, the VM raises a permission request. This example shows how to handle those requests—either fully server-side (auto-approve) or by forwarding them to a client for a human-in-the-loop decision (selective approval). Reach for this when you need to control what an agent is allowed to do mid-session.\n\n## How it works\n\nPermissions flow through two complementary hooks:\n\n- **Server-side (`onPermissionRequest`)**: a hook on `agentOS({ ... })` runs for every request before it reaches any client. Inspect `request.description` and `request.params` to approve, log, or filter requests in fully automated pipelines—no client round-trip needed.\n- **Client-side (`permissionRequest` event)**: requests the server forwards reach the client over a live `agent.connect()` connection. The client decides and calls `agent.respondPermission(sessionId, permissionId, \"once\" | \"reject\")` to allow a single action or deny it.\n\nThe `selective` variants combine both: the server handles some requests itself and forwards the rest to the client. A local `pi` software fixture stands in for a real agent package so the example runs self-contained.\n\n## Run it\n\n```bash\nnpm install\n# Auto-approve everything server-side:\nnpx tsx server.ts # in one terminal\nnpx tsx client.ts # in another\n\n# Or run the auto-approve / selective variants:\nnpx tsx auto-approve.ts & npx tsx auto-approve-client.ts\nnpx tsx selective.ts & npx tsx selective-client.ts\n```\n\nThe agent runs its prompt and each permission request is approved, rejected, or logged according to the hook you chose.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/approvals)\n","25f8ea596bbebdb9",{"html":449,"metadata":450},"\u003Cp>When an agent wants to read a file, write output, or run a command, the VM raises a permission request. This example shows how to handle those requests—either fully server-side (auto-approve) or by forwarding them to a client for a human-in-the-loop decision (selective approval). Reach for this when you need to control what an agent is allowed to do mid-session.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Permissions flow through two complementary hooks:\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Server-side (\u003Ccode>onPermissionRequest\u003C/code>)\u003C/strong>: a hook on \u003Ccode>agentOS({ ... })\u003C/code> runs for every request before it reaches any client. Inspect \u003Ccode>request.description\u003C/code> and \u003Ccode>request.params\u003C/code> to approve, log, or filter requests in fully automated pipelines—no client round-trip needed.\u003C/li>\n\u003Cli>\u003Cstrong>Client-side (\u003Ccode>permissionRequest\u003C/code> event)\u003C/strong>: requests the server forwards reach the client over a live \u003Ccode>agent.connect()\u003C/code> connection. The client decides and calls \u003Ccode>agent.respondPermission(sessionId, permissionId, \"once\" | \"reject\")\u003C/code> to allow a single action or deny it.\u003C/li>\n\u003C/ul>\n\u003Cp>The \u003Ccode>selective\u003C/code> variants combine both: the server handles some requests itself and forwards the rest to the client. A local \u003Ccode>pi\u003C/code> software fixture stands in for a real agent package so the example runs self-contained.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\n# Auto-approve everything server-side:\nnpx tsx server.ts # in one terminal\nnpx tsx client.ts # in another\n\n# Or run the auto-approve / selective variants:\nnpx tsx auto-approve.ts & npx tsx auto-approve-client.ts\nnpx tsx selective.ts & npx tsx selective-client.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># Auto-approve everything server-side:\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in one terminal\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in another\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># Or run the auto-approve / selective variants:\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> auto-approve.ts\u003C/span>\u003Cspan style="color:#BFBDB6B3"> &#x26;\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> auto-approve-client.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> selective.ts\u003C/span>\u003Cspan style="color:#BFBDB6B3"> &#x26;\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> selective-client.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\n# Auto-approve everything server-side:\nnpx tsx server.ts # in one terminal\nnpx tsx client.ts # in another\n\n# Or run the auto-approve / selective variants:\nnpx tsx auto-approve.ts & npx tsx auto-approve-client.ts\nnpx tsx selective.ts & npx tsx selective-client.ts\n\u003C/code>\u003C/pre>\n\u003Cp>The agent runs its prompt and each permission request is approved, rejected, or logged according to the hook you chose.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/approvals\">View source on GitHub\u003C/a>\u003C/p>",{"headings":451,"localImagePaths":455,"remoteImagePaths":456,"frontmatter":457},[452,453,454],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/authentication",{"id":458,"data":460,"body":462,"digest":463,"rendered":464},{"title":31,"description":461},"Validate client credentials server-side with onBeforeConnect connection hooks.","\n# Authentication\n\nGate access to your VMs by validating client credentials on the server before any connection is established. Reach for this whenever clients must present a token (a JWT, API key, or session ID) that you verify before letting them create sessions or send prompts.\n\n## How it works\n\nThe client passes credentials as connection `params` when it calls `getOrCreate`. Those params are forwarded to the server, where an `onBeforeConnect` hook inspects them and rejects the connection by throwing. Because `params` is typed as `unknown` on the wire, the hook is the real enforcement point: it checks the token's shape and validity (signature, lookup, expiry) and either returns to admit the connection or throws to deny it. Once admitted, every action on the handle runs against that authenticated connection.\n\n## Run it\n\n```bash\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # in one terminal\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # in another\n```\n\nA client with a valid `authToken` connects and lists the working directory; one with a missing or empty token is rejected before any session is created.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/authentication)\n","7145f36207c2a996",{"html":465,"metadata":466},"\u003Ch1 id=\"authentication\">Authentication\u003C/h1>\n\u003Cp>Gate access to your VMs by validating client credentials on the server before any connection is established. Reach for this whenever clients must present a token (a JWT, API key, or session ID) that you verify before letting them create sessions or send prompts.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The client passes credentials as connection \u003Ccode>params\u003C/code> when it calls \u003Ccode>getOrCreate\u003C/code>. Those params are forwarded to the server, where an \u003Ccode>onBeforeConnect\u003C/code> hook inspects them and rejects the connection by throwing. Because \u003Ccode>params\u003C/code> is typed as \u003Ccode>unknown\u003C/code> on the wire, the hook is the real enforcement point: it checks the token’s shape and validity (signature, lookup, expiry) and either returns to admit the connection or throws to deny it. Once admitted, every action on the handle runs against that authenticated connection.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # in one terminal\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # in another\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in one terminal\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in another\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # in one terminal\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # in another\n\u003C/code>\u003C/pre>\n\u003Cp>A client with a valid \u003Ccode>authToken\u003C/code> connects and lists the working directory; one with a missing or empty token is rejected before any session is created.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/authentication\">View source on GitHub\u003C/a>\u003C/p>",{"headings":467,"localImagePaths":474,"remoteImagePaths":475,"frontmatter":476},[468,471,472,473],{"depth":469,"slug":470,"text":31},1,"authentication",{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/bindings",{"id":477,"data":479,"body":481,"digest":482,"rendered":483},{"title":55,"description":480},"Expose host functions to the agent as CLI commands via Zod-typed bindings.","\nGive an agent access to your own host code—API calls, database lookups, internal services—without writing a tool from scratch. Reach for bindings when the agent needs to call back into your application and you want type-safe inputs plus an auto-generated CLI surface inside the VM.\n\n## How it works\n\nA binding group bundles a `name`, a `description`, and a map of named tools. Each tool declares a Zod `inputSchema`, an `execute` handler that runs on the host, and optional `examples`. You pass the groups to `agentOS({ toolKits: [...] })`, and Agent OS exposes every group to the agent as a CLI command at `/usr/local/bin/agentos-{name}` inside the VM. When the agent invokes the command, the Zod schema validates the arguments and the handler executes host-side, returning the result back to the guest. The client side stays thin: create a session and send a prompt, and the agent decides when to call the binding.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts\n# in another terminal:\nnpx tsx client.ts\n```\n\nThe agent receives the prompt, calls the `weather` forecast binding, and answers using the host-side result.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/bindings)\n","1a8b55da59db2fdd",{"html":484,"metadata":485},"\u003Cp>Give an agent access to your own host code—API calls, database lookups, internal services—without writing a tool from scratch. Reach for bindings when the agent needs to call back into your application and you want type-safe inputs plus an auto-generated CLI surface inside the VM.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A binding group bundles a \u003Ccode>name\u003C/code>, a \u003Ccode>description\u003C/code>, and a map of named tools. Each tool declares a Zod \u003Ccode>inputSchema\u003C/code>, an \u003Ccode>execute\u003C/code> handler that runs on the host, and optional \u003Ccode>examples\u003C/code>. You pass the groups to \u003Ccode>agentOS({ toolKits: [...] })\u003C/code>, and Agent OS exposes every group to the agent as a CLI command at \u003Ccode>/usr/local/bin/agentos-{name}\u003C/code> inside the VM. When the agent invokes the command, the Zod schema validates the arguments and the handler executes host-side, returning the result back to the guest. The client side stays thin: create a session and send a prompt, and the agent decides when to call the binding.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts\n# in another terminal:\nnpx tsx client.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># in another terminal:\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts\n# in another terminal:\nnpx tsx client.ts\n\u003C/code>\u003C/pre>\n\u003Cp>The agent receives the prompt, calls the \u003Ccode>weather\u003C/code> forecast binding, and answers using the host-side result.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/bindings\">View source on GitHub\u003C/a>\u003C/p>",{"headings":486,"localImagePaths":490,"remoteImagePaths":491,"frontmatter":492},[487,488,489],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/claude",{"id":493,"data":495,"body":498,"digest":499,"rendered":500},{"title":496,"description":497},"Claude Agent","Run the Claude Code agent in a session using an Anthropic API key.","\nRun the Claude Code agent inside a VM session and drive it with prompts. Reach for this when you want a coding agent that reads, writes, and runs commands in an isolated environment instead of calling the model API directly.\n\n## How it works\n\nThe server registers a VM with the Claude Code software and starts the registry. The client creates a session against the `claude` agent, passing `ANTHROPIC_API_KEY` through the session env, then calls `sendPrompt` to get the agent's response. From there you can layer on extras: drop a `SKILL.md` into `~/.claude/skills/` before creating the session and the agent discovers it automatically, or pass `mcpServers` (local child processes or remote URLs) to expose more tools. Pre-install local MCP servers with `exec` so first-run `npx` output does not corrupt the stdio handshake.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-ant-... npm run start\n```\n\nThe agent answers the prompt and prints its response to the console.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/claude)\n","8060529e4208a811",{"html":501,"metadata":502},"\u003Cp>Run the Claude Code agent inside a VM session and drive it with prompts. Reach for this when you want a coding agent that reads, writes, and runs commands in an isolated environment instead of calling the model API directly.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The server registers a VM with the Claude Code software and starts the registry. The client creates a session against the \u003Ccode>claude\u003C/code> agent, passing \u003Ccode>ANTHROPIC_API_KEY\u003C/code> through the session env, then calls \u003Ccode>sendPrompt\u003C/code> to get the agent’s response. From there you can layer on extras: drop a \u003Ccode>SKILL.md\u003C/code> into \u003Ccode>~/.claude/skills/\u003C/code> before creating the session and the agent discovers it automatically, or pass \u003Ccode>mcpServers\u003C/code> (local child processes or remote URLs) to expose more tools. Pre-install local MCP servers with \u003Ccode>exec\u003C/code> so first-run \u003Ccode>npx\u003C/code> output does not corrupt the stdio handshake.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-ant-... npm run start\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-ant-...\u003C/span>\u003Cspan style="color:#59C2FF"> npm\u003C/span>\u003Cspan style="color:#AAD94C"> run\u003C/span>\u003Cspan style="color:#AAD94C"> start\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-ant-... npm run start\n\u003C/code>\u003C/pre>\n\u003Cp>The agent answers the prompt and prints its response to the console.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/claude\">View source on GitHub\u003C/a>\u003C/p>",{"headings":503,"localImagePaths":507,"remoteImagePaths":508,"frontmatter":509},[504,505,506],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/codex",{"id":510,"data":512,"body":515,"digest":516,"rendered":517},{"title":513,"description":514},"Codex Agent","Run the Codex agent in a session using an OpenAI API key.","\n# Codex Agent\n\nRun OpenAI's Codex agent inside a VM session and prompt it with natural language. Reach for this when you want a coding agent that can read and act on the VM's filesystem, backed by your own OpenAI API key.\n\n## How it works\n\nRegister the `codex` software with `agentOS({ software: [codex] })` so the VM knows how to launch the agent. The client calls `agent.createSession(\"codex\", ...)`, passing `OPENAI_API_KEY` through `env`, then drives the agent with `agent.sendPrompt`. Two optional extensions build on the same flow: drop a `SKILL.md` into `/home/agentos/.codex/skills/` before creating the session and the agent auto-discovers it, or pass `mcpServers` (local child-process or remote URL) to expose extra tools.\n\n## Run it\n\n```bash\nnpm install\nexport OPENAI_API_KEY=sk-...\nnpx tsx server.ts # starts the registry on http://localhost:6420\nnpx tsx client.ts # creates a Codex session and prints the agent's reply\n```\n\nYou should see the agent describe the files in its working directory.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/codex)\n","c44c8ecdf080f117",{"html":518,"metadata":519},"\u003Ch1 id=\"codex-agent\">Codex Agent\u003C/h1>\n\u003Cp>Run OpenAI’s Codex agent inside a VM session and prompt it with natural language. Reach for this when you want a coding agent that can read and act on the VM’s filesystem, backed by your own OpenAI API key.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Register the \u003Ccode>codex\u003C/code> software with \u003Ccode>agentOS({ software: [codex] })\u003C/code> so the VM knows how to launch the agent. The client calls \u003Ccode>agent.createSession(\"codex\", ...)\u003C/code>, passing \u003Ccode>OPENAI_API_KEY\u003C/code> through \u003Ccode>env\u003C/code>, then drives the agent with \u003Ccode>agent.sendPrompt\u003C/code>. Two optional extensions build on the same flow: drop a \u003Ccode>SKILL.md\u003C/code> into \u003Ccode>/home/agentos/.codex/skills/\u003C/code> before creating the session and the agent auto-discovers it, or pass \u003Ccode>mcpServers\u003C/code> (local child-process or remote URL) to expose extra tools.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nexport OPENAI_API_KEY=sk-...\nnpx tsx server.ts # starts the registry on http://localhost:6420\nnpx tsx client.ts # creates a Codex session and prints the agent's reply\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#FF8F40">export\u003C/span>\u003Cspan style="color:#BFBDB6"> OPENAI_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#BFBDB6">sk-...\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # starts the registry on http://localhost:6420\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # creates a Codex session and prints the agent's reply\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nexport OPENAI_API_KEY=sk-...\nnpx tsx server.ts # starts the registry on http://localhost:6420\nnpx tsx client.ts # creates a Codex session and prints the agent's reply\n\u003C/code>\u003C/pre>\n\u003Cp>You should see the agent describe the files in its working directory.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/codex\">View source on GitHub\u003C/a>\u003C/p>",{"headings":520,"localImagePaths":526,"remoteImagePaths":527,"frontmatter":528},[521,523,524,525],{"depth":469,"slug":522,"text":513},"codex-agent",{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/core",{"id":529,"data":531,"body":534,"digest":535,"rendered":536},{"title":532,"description":533},"Core","Core AgentOs API: exec, config reference, lifecycle hooks, and mounts.","\nThe core AgentOs API surface in one place: define a VM server, connect a typed client, and drive VMs for exec, filesystem, processes, agent sessions, networking, and cron. Reach for this when you want a reference of what a `handle` can do and how the server is configured.\n\n## How it works\n\n`agentOS({ ... })` defines a VM with its mounts, software, lifecycle hooks, and preview/network settings, then `setup({ use: { vm } }).start()` exposes it over the wire. On the client, `createClient\u003Ctypeof registry>()` gives a typed `client`, and `client.vm.getOrCreate(id)` returns a `handle`. Everything runs through that handle: `exec`/`spawn` for processes, `readFile`/`writeFiles`/`readdirRecursive` for the filesystem, `createSession`/`sendPrompt` for agents, `openShell` for interactive terminals, `vmFetch` for in-VM servers, and `scheduleCron` for jobs. `handle.connect()` opens an event stream for process output, session events, permission requests, and cron events.\n\n- `server.ts` / `config-reference.ts` — VM definition and the full config surface (mounts, software, loopback exemptions, preview token lifetimes, hooks).\n- `hooks.ts` — server-side lifecycle hooks like `onSessionEvent`.\n- `mounts.ts` — host-directory and S3 mount descriptors.\n- `client.ts` — every client capability against a `handle`.\n\n## Run it\n\n```sh\nnpm install\nnpx tsx server.ts # start the VM server, then run client.ts against it\n```\n\nThe server listens on `http://localhost:6420`; the client connects, creates a VM, and exercises exec, filesystem, sessions, and more.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/core)\n","784f3fa0088756f7",{"html":537,"metadata":538},"\u003Cp>The core AgentOs API surface in one place: define a VM server, connect a typed client, and drive VMs for exec, filesystem, processes, agent sessions, networking, and cron. Reach for this when you want a reference of what a \u003Ccode>handle\u003C/code> can do and how the server is configured.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>\u003Ccode>agentOS({ ... })\u003C/code> defines a VM with its mounts, software, lifecycle hooks, and preview/network settings, then \u003Ccode>setup({ use: { vm } }).start()\u003C/code> exposes it over the wire. On the client, \u003Ccode>createClient<typeof registry>()\u003C/code> gives a typed \u003Ccode>client\u003C/code>, and \u003Ccode>client.vm.getOrCreate(id)\u003C/code> returns a \u003Ccode>handle\u003C/code>. Everything runs through that handle: \u003Ccode>exec\u003C/code>/\u003Ccode>spawn\u003C/code> for processes, \u003Ccode>readFile\u003C/code>/\u003Ccode>writeFiles\u003C/code>/\u003Ccode>readdirRecursive\u003C/code> for the filesystem, \u003Ccode>createSession\u003C/code>/\u003Ccode>sendPrompt\u003C/code> for agents, \u003Ccode>openShell\u003C/code> for interactive terminals, \u003Ccode>vmFetch\u003C/code> for in-VM servers, and \u003Ccode>scheduleCron\u003C/code> for jobs. \u003Ccode>handle.connect()\u003C/code> opens an event stream for process output, session events, permission requests, and cron events.\u003C/p>\n\u003Cul>\n\u003Cli>\u003Ccode>server.ts\u003C/code> / \u003Ccode>config-reference.ts\u003C/code> — VM definition and the full config surface (mounts, software, loopback exemptions, preview token lifetimes, hooks).\u003C/li>\n\u003Cli>\u003Ccode>hooks.ts\u003C/code> — server-side lifecycle hooks like \u003Ccode>onSessionEvent\u003C/code>.\u003C/li>\n\u003Cli>\u003Ccode>mounts.ts\u003C/code> — host-directory and S3 mount descriptors.\u003C/li>\n\u003Cli>\u003Ccode>client.ts\u003C/code> — every client capability against a \u003Ccode>handle\u003C/code>.\u003C/li>\n\u003C/ul>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpx tsx server.ts # start the VM server, then run client.ts against it\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the VM server, then run client.ts against it\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpx tsx server.ts # start the VM server, then run client.ts against it\n\u003C/code>\u003C/pre>\n\u003Cp>The server listens on \u003Ccode>http://localhost:6420\u003C/code>; the client connects, creates a VM, and exercises exec, filesystem, sessions, and more.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/core\">View source on GitHub\u003C/a>\u003C/p>",{"headings":539,"localImagePaths":543,"remoteImagePaths":544,"frontmatter":545},[540,541,542],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/crash-course",{"id":546,"data":548,"body":550,"digest":551,"rendered":552},{"title":79,"description":549},"Guided tour through core capabilities: sessions, filesystem, processes, networking, cron, permissions, and multiplayer.","\n# Crash Course\n\nA guided tour through the core capabilities of Agent OS, one small client per feature. Reach for it when you want to see the whole surface area — sessions, filesystem, processes, networking, cron, permissions, and multiplayer — without reading the full docs.\n\n## How it works\n\nA single `server.ts` stands up an Agent OS registry with the `pi` agent software, and each `*-client.ts` file connects to it to exercise one capability:\n\n- **Sessions** (`minimal-client.ts`, `sessions-client.ts`) — create a session, stream `sessionEvent`s, send prompts, and read back files the agent wrote.\n- **Filesystem** (`filesystem-client.ts`) — `writeFile`, `readFile`, and recursive directory listing.\n- **Processes** (`processes-client.ts`) — one-shot `exec` plus long-running `spawn` with streamed `processOutput`.\n- **Networking** (`networking-client.ts`) — `vmFetch` against an in-VM service and signed public preview URLs.\n- **Cron** (`cron-client.ts`) — schedule recurring `exec` commands and agent sessions.\n- **Permissions** (`permissions-client.ts`, `permissions-server.ts`) — handle permission requests client-side (human-in-the-loop) or auto-approve server-side.\n- **Multiplayer** (`multiplayer-client.ts`) — two clients observing the same shared agent session.\n- **Agent-to-agent** (`agent-to-agent-*.ts`) — a coder agent calls a `review` binding that drives a separate reviewer agent.\n\n## Run it\n\n```bash\nnpm install\nnpx tsx server.ts # start the registry\nnpx tsx minimal-client.ts # then run any client in another terminal\n```\n\nEach client prints its results — streamed events, file contents, process output, or URLs — to the console.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course)\n","823449d34f6b2fd2",{"html":553,"metadata":554},"\u003Ch1 id=\"crash-course\">Crash Course\u003C/h1>\n\u003Cp>A guided tour through the core capabilities of Agent OS, one small client per feature. Reach for it when you want to see the whole surface area — sessions, filesystem, processes, networking, cron, permissions, and multiplayer — without reading the full docs.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A single \u003Ccode>server.ts\u003C/code> stands up an Agent OS registry with the \u003Ccode>pi\u003C/code> agent software, and each \u003Ccode>*-client.ts\u003C/code> file connects to it to exercise one capability:\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Sessions\u003C/strong> (\u003Ccode>minimal-client.ts\u003C/code>, \u003Ccode>sessions-client.ts\u003C/code>) — create a session, stream \u003Ccode>sessionEvent\u003C/code>s, send prompts, and read back files the agent wrote.\u003C/li>\n\u003Cli>\u003Cstrong>Filesystem\u003C/strong> (\u003Ccode>filesystem-client.ts\u003C/code>) — \u003Ccode>writeFile\u003C/code>, \u003Ccode>readFile\u003C/code>, and recursive directory listing.\u003C/li>\n\u003Cli>\u003Cstrong>Processes\u003C/strong> (\u003Ccode>processes-client.ts\u003C/code>) — one-shot \u003Ccode>exec\u003C/code> plus long-running \u003Ccode>spawn\u003C/code> with streamed \u003Ccode>processOutput\u003C/code>.\u003C/li>\n\u003Cli>\u003Cstrong>Networking\u003C/strong> (\u003Ccode>networking-client.ts\u003C/code>) — \u003Ccode>vmFetch\u003C/code> against an in-VM service and signed public preview URLs.\u003C/li>\n\u003Cli>\u003Cstrong>Cron\u003C/strong> (\u003Ccode>cron-client.ts\u003C/code>) — schedule recurring \u003Ccode>exec\u003C/code> commands and agent sessions.\u003C/li>\n\u003Cli>\u003Cstrong>Permissions\u003C/strong> (\u003Ccode>permissions-client.ts\u003C/code>, \u003Ccode>permissions-server.ts\u003C/code>) — handle permission requests client-side (human-in-the-loop) or auto-approve server-side.\u003C/li>\n\u003Cli>\u003Cstrong>Multiplayer\u003C/strong> (\u003Ccode>multiplayer-client.ts\u003C/code>) — two clients observing the same shared agent session.\u003C/li>\n\u003Cli>\u003Cstrong>Agent-to-agent\u003C/strong> (\u003Ccode>agent-to-agent-*.ts\u003C/code>) — a coder agent calls a \u003Ccode>review\u003C/code> binding that drives a separate reviewer agent.\u003C/li>\n\u003C/ul>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nnpx tsx server.ts # start the registry\nnpx tsx minimal-client.ts # then run any client in another terminal\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the registry\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> minimal-client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # then run any client in another terminal\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nnpx tsx server.ts # start the registry\nnpx tsx minimal-client.ts # then run any client in another terminal\n\u003C/code>\u003C/pre>\n\u003Cp>Each client prints its results — streamed events, file contents, process output, or URLs — to the console.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/crash-course\">View source on GitHub\u003C/a>\u003C/p>",{"headings":555,"localImagePaths":561,"remoteImagePaths":562,"frontmatter":563},[556,558,559,560],{"depth":469,"slug":557,"text":79},"crash-course",{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/cron",{"id":564,"data":566,"body":569,"digest":570,"rendered":571},{"title":567,"description":568},"Cron","Schedule recurring commands and agent sessions with cron, including overlap handling, monitoring, and cancellation.","\nRun work on a schedule inside a VM — a shell command on a fixed interval, or a recurring agent session that reviews logs, triages issues, or processes a queue. Reach for this when you need background jobs that fire on a cron expression instead of on demand.\n\n## How it works\n\nEach VM handle exposes `scheduleCron({ schedule, action, overlap })`. The `schedule` is a standard cron expression, and the `action` is either an `exec` (run a command with args) or a `session` (spawn an agent of a given `agentType` with a `prompt`). The `overlap` policy decides what happens when a run is still going when the next tick arrives: `skip` drops the new run, `queue` lines it up to run after. Scheduling returns a job `id` you can later pass to `cancelCronJob`, and `listCronJobs` enumerates everything registered on the VM. Connecting to the handle and listening for `cronEvent` streams each run's lifecycle so you can monitor execution.\n\n## Run it\n\n```sh\nnpm install\nnpx tsx server.ts # start the registry, then run any example, e.g. npx tsx schedule-session.ts\n```\n\nYou should see the cron job registered and its `id` printed; scheduled runs fire on their interval and surface as `cronEvent`s.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/cron)\n","a729c2d2adad946a",{"html":572,"metadata":573},"\u003Cp>Run work on a schedule inside a VM — a shell command on a fixed interval, or a recurring agent session that reviews logs, triages issues, or processes a queue. Reach for this when you need background jobs that fire on a cron expression instead of on demand.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Each VM handle exposes \u003Ccode>scheduleCron({ schedule, action, overlap })\u003C/code>. The \u003Ccode>schedule\u003C/code> is a standard cron expression, and the \u003Ccode>action\u003C/code> is either an \u003Ccode>exec\u003C/code> (run a command with args) or a \u003Ccode>session\u003C/code> (spawn an agent of a given \u003Ccode>agentType\u003C/code> with a \u003Ccode>prompt\u003C/code>). The \u003Ccode>overlap\u003C/code> policy decides what happens when a run is still going when the next tick arrives: \u003Ccode>skip\u003C/code> drops the new run, \u003Ccode>queue\u003C/code> lines it up to run after. Scheduling returns a job \u003Ccode>id\u003C/code> you can later pass to \u003Ccode>cancelCronJob\u003C/code>, and \u003Ccode>listCronJobs\u003C/code> enumerates everything registered on the VM. Connecting to the handle and listening for \u003Ccode>cronEvent\u003C/code> streams each run’s lifecycle so you can monitor execution.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpx tsx server.ts # start the registry, then run any example, e.g. npx tsx schedule-session.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the registry, then run any example, e.g. npx tsx schedule-session.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpx tsx server.ts # start the registry, then run any example, e.g. npx tsx schedule-session.ts\n\u003C/code>\u003C/pre>\n\u003Cp>You should see the cron job registered and its \u003Ccode>id\u003C/code> printed; scheduled runs fire on their interval and surface as \u003Ccode>cronEvent\u003C/code>s.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/cron\">View source on GitHub\u003C/a>\u003C/p>",{"headings":574,"localImagePaths":578,"remoteImagePaths":579,"frontmatter":580},[575,576,577],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/filesystem",{"id":581,"data":583,"body":585,"digest":586,"rendered":587},{"title":111,"description":584},"Filesystem access: host-side file APIs, VFS isolation, and mounting memory, host directories, S3, and Google Drive.","\nEvery VM gets an isolated virtual filesystem (VFS) that you drive from the host. Reach for this when you need to seed files into a VM, read results back out, or expose external storage to the guest without leaking the host disk.\n\n## How it works\n\nThe host client exposes file APIs directly on a VM handle — `writeFile`/`readFile` for single files, `writeFiles`/`readFiles` for batches, plus `mkdir`, `readdir`, `readdirRecursive`, `stat`, `exists`, `move`, and `deleteFile`. Bytes you write land in the kernel's in-memory VFS, which the guest sees through the normal `node:fs` API; the real host disk is never exposed, so a path that exists in the VFS does not exist on the host.\n\nTo bridge in external storage, declare `mounts` on `agentOS({ ... })`. Each mount maps a guest path to a plugin: `memory` for scratch space, `host_dir` for a host directory (optionally `readOnly`), `s3` for a bucket/prefix, or `google_drive` for a Drive folder. The guest reads and writes those paths like any other directory.\n\n## Run it\n\n```bash\nnpm install\nnpx tsx server.ts # start the VM host\nnpx tsx operations.ts # in another shell: exercise the file APIs\n```\n\n`operations.ts` writes, reads, lists, moves, and deletes files; `isolation.ts` shows the VFS is sealed from the host disk; the `mount-*.ts` servers swap in different storage backends.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/filesystem)\n","d5bc0a592860aec4",{"html":588,"metadata":589},"\u003Cp>Every VM gets an isolated virtual filesystem (VFS) that you drive from the host. Reach for this when you need to seed files into a VM, read results back out, or expose external storage to the guest without leaking the host disk.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The host client exposes file APIs directly on a VM handle — \u003Ccode>writeFile\u003C/code>/\u003Ccode>readFile\u003C/code> for single files, \u003Ccode>writeFiles\u003C/code>/\u003Ccode>readFiles\u003C/code> for batches, plus \u003Ccode>mkdir\u003C/code>, \u003Ccode>readdir\u003C/code>, \u003Ccode>readdirRecursive\u003C/code>, \u003Ccode>stat\u003C/code>, \u003Ccode>exists\u003C/code>, \u003Ccode>move\u003C/code>, and \u003Ccode>deleteFile\u003C/code>. Bytes you write land in the kernel’s in-memory VFS, which the guest sees through the normal \u003Ccode>node:fs\u003C/code> API; the real host disk is never exposed, so a path that exists in the VFS does not exist on the host.\u003C/p>\n\u003Cp>To bridge in external storage, declare \u003Ccode>mounts\u003C/code> on \u003Ccode>agentOS({ ... })\u003C/code>. Each mount maps a guest path to a plugin: \u003Ccode>memory\u003C/code> for scratch space, \u003Ccode>host_dir\u003C/code> for a host directory (optionally \u003Ccode>readOnly\u003C/code>), \u003Ccode>s3\u003C/code> for a bucket/prefix, or \u003Ccode>google_drive\u003C/code> for a Drive folder. The guest reads and writes those paths like any other directory.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nnpx tsx server.ts # start the VM host\nnpx tsx operations.ts # in another shell: exercise the file APIs\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the VM host\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> operations.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in another shell: exercise the file APIs\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nnpx tsx server.ts # start the VM host\nnpx tsx operations.ts # in another shell: exercise the file APIs\n\u003C/code>\u003C/pre>\n\u003Cp>\u003Ccode>operations.ts\u003C/code> writes, reads, lists, moves, and deletes files; \u003Ccode>isolation.ts\u003C/code> shows the VFS is sealed from the host disk; the \u003Ccode>mount-*.ts\u003C/code> servers swap in different storage backends.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/filesystem\">View source on GitHub\u003C/a>\u003C/p>",{"headings":590,"localImagePaths":594,"remoteImagePaths":595,"frontmatter":596},[591,592,593],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/llm-credentials",{"id":597,"data":599,"body":601,"digest":602,"rendered":603},{"title":141,"description":600},"Pass LLM provider keys per session via env, including a per-tenant credential pattern.","\nA VM never inherits the host `process.env`, so LLM provider keys must be handed to each session explicitly. Reach for this when your agent needs an `ANTHROPIC_API_KEY` (or any provider secret) and you want that key scoped to a single session — or to a single tenant — rather than baked into the server.\n\n## How it works\n\nThe server declares the agent software but holds no credentials. The client passes keys through the `env` option on `createSession`, which injects them into that session's VM only. For multi-tenant setups, give each tenant an isolated VM keyed by their id and resolve their key from your own credential store at session-creation time. Keys live on the server and are never sent to the client.\n\n## Run it\n\n```bash\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # then, in another shell:\nANTHROPIC_API_KEY=sk-... npx tsx client.ts\n```\n\nThe client prints a new session id; the agent inside the VM sees the key via its environment. See `per-tenant.ts` for the per-tenant variant.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/llm-credentials)\n","9aca20bf57234cf7",{"html":604,"metadata":605},"\u003Cp>A VM never inherits the host \u003Ccode>process.env\u003C/code>, so LLM provider keys must be handed to each session explicitly. Reach for this when your agent needs an \u003Ccode>ANTHROPIC_API_KEY\u003C/code> (or any provider secret) and you want that key scoped to a single session — or to a single tenant — rather than baked into the server.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The server declares the agent software but holds no credentials. The client passes keys through the \u003Ccode>env\u003C/code> option on \u003Ccode>createSession\u003C/code>, which injects them into that session’s VM only. For multi-tenant setups, give each tenant an isolated VM keyed by their id and resolve their key from your own credential store at session-creation time. Keys live on the server and are never sent to the client.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # then, in another shell:\nANTHROPIC_API_KEY=sk-... npx tsx client.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # then, in another shell:\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # then, in another shell:\nANTHROPIC_API_KEY=sk-... npx tsx client.ts\n\u003C/code>\u003C/pre>\n\u003Cp>The client prints a new session id; the agent inside the VM sees the key via its environment. See \u003Ccode>per-tenant.ts\u003C/code> for the per-tenant variant.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/llm-credentials\">View source on GitHub\u003C/a>\u003C/p>",{"headings":606,"localImagePaths":610,"remoteImagePaths":611,"frontmatter":612},[607,608,609],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/multiplayer",{"id":613,"data":615,"body":617,"digest":618,"rendered":619},{"title":157,"description":616},"Multiple clients observing one session: shared output, collaborative input, and reconnect replay.","\nRun one agent session and let several clients watch and drive it at once. Reach for this when a session needs more than one viewer — pair programming, a shared review session, or a dashboard mirroring what an agent is doing live.\n\n## How it works\n\nClients connect to the same VM actor by id (`getOrCreate(\"shared-agent\")`), so they share one session rather than spawning their own. Each connection subscribes to the same event stream — `sessionEvent`, `processOutput`, and `shellData` all fan out to every connected client. One client can create the session and `sendPrompt`, while others observe the streaming response without driving it. Because the server fans events out from a single session, the `onSessionEvent` server hook still fires once per event regardless of how many clients are attached. Every event carries a sequence number, so a client that drops can call `getSequencedEvents({ since })` to replay what it missed before resuming the live stream.\n\n## Run it\n\n```sh\nnpm install\n# terminal 1 — start the server\nnpx tsx server.ts\n# terminal 2+ — attach observers / drivers\nnpx tsx collaborative.ts\n```\n\nMultiple clients print the same session events; an observer sees the driver's prompt response stream in real time.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/multiplayer)\n","bc98a070dad72778",{"html":620,"metadata":621},"\u003Cp>Run one agent session and let several clients watch and drive it at once. Reach for this when a session needs more than one viewer — pair programming, a shared review session, or a dashboard mirroring what an agent is doing live.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Clients connect to the same VM actor by id (\u003Ccode>getOrCreate(\"shared-agent\")\u003C/code>), so they share one session rather than spawning their own. Each connection subscribes to the same event stream — \u003Ccode>sessionEvent\u003C/code>, \u003Ccode>processOutput\u003C/code>, and \u003Ccode>shellData\u003C/code> all fan out to every connected client. One client can create the session and \u003Ccode>sendPrompt\u003C/code>, while others observe the streaming response without driving it. Because the server fans events out from a single session, the \u003Ccode>onSessionEvent\u003C/code> server hook still fires once per event regardless of how many clients are attached. Every event carries a sequence number, so a client that drops can call \u003Ccode>getSequencedEvents({ since })\u003C/code> to replay what it missed before resuming the live stream.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\n# terminal 1 — start the server\nnpx tsx server.ts\n# terminal 2+ — attach observers / drivers\nnpx tsx collaborative.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># terminal 1 — start the server\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># terminal 2+ — attach observers / drivers\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> collaborative.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\n# terminal 1 — start the server\nnpx tsx server.ts\n# terminal 2+ — attach observers / drivers\nnpx tsx collaborative.ts\n\u003C/code>\u003C/pre>\n\u003Cp>Multiple clients print the same session events; an observer sees the driver’s prompt response stream in real time.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/multiplayer\">View source on GitHub\u003C/a>\u003C/p>",{"headings":622,"localImagePaths":626,"remoteImagePaths":627,"frontmatter":628},[623,624,625],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/networking",{"id":629,"data":631,"body":633,"digest":634,"rendered":635},{"title":373,"description":632},"VM networking: loopback servers, fetch from inside and outside the VM, and signed preview URLs.","\n# Networking\n\nRun a service inside a VM and reach it — from the client and from the public web. Reach for this when an agent spins up a dev server, API, or web app that you need to call or share.\n\n## How it works\n\nA process inside the VM binds a normal loopback port (e.g. `3000`), exactly like any Node server. The client reaches it with `agent.vmFetch(port, path, options)`, which proxies an HTTP request straight to that loopback port without exposing it to the network. To expose a port beyond loopback, set `loopbackExemptPorts` on the VM config. For external sharing, `agent.createSignedPreviewUrl(port, expiresInSeconds)` mints a short-lived signed URL; the `preview` config sets default and maximum lifetimes, and old tokens fall off automatically as they expire.\n\n## Run it\n\n```bash\nnpm install\n# Start the VM host\nnpx tsx server.ts\n# In another terminal, run a server in the VM and fetch it\nnpx tsx client-run-server.ts\nnpx tsx client-fetch.ts\n```\n\nExpect a `200` status and `Hello from inside the VM` printed by the fetch client.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/networking)\n","41bd8e2032d7c955",{"html":636,"metadata":637},"\u003Ch1 id=\"networking\">Networking\u003C/h1>\n\u003Cp>Run a service inside a VM and reach it — from the client and from the public web. Reach for this when an agent spins up a dev server, API, or web app that you need to call or share.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A process inside the VM binds a normal loopback port (e.g. \u003Ccode>3000\u003C/code>), exactly like any Node server. The client reaches it with \u003Ccode>agent.vmFetch(port, path, options)\u003C/code>, which proxies an HTTP request straight to that loopback port without exposing it to the network. To expose a port beyond loopback, set \u003Ccode>loopbackExemptPorts\u003C/code> on the VM config. For external sharing, \u003Ccode>agent.createSignedPreviewUrl(port, expiresInSeconds)\u003C/code> mints a short-lived signed URL; the \u003Ccode>preview\u003C/code> config sets default and maximum lifetimes, and old tokens fall off automatically as they expire.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\n# Start the VM host\nnpx tsx server.ts\n# In another terminal, run a server in the VM and fetch it\nnpx tsx client-run-server.ts\nnpx tsx client-fetch.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># Start the VM host\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># In another terminal, run a server in the VM and fetch it\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client-run-server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client-fetch.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\n# Start the VM host\nnpx tsx server.ts\n# In another terminal, run a server in the VM and fetch it\nnpx tsx client-run-server.ts\nnpx tsx client-fetch.ts\n\u003C/code>\u003C/pre>\n\u003Cp>Expect a \u003Ccode>200\u003C/code> status and \u003Ccode>Hello from inside the VM\u003C/code> printed by the fetch client.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/networking\">View source on GitHub\u003C/a>\u003C/p>",{"headings":638,"localImagePaths":644,"remoteImagePaths":645,"frontmatter":646},[639,641,642,643],{"depth":469,"slug":640,"text":373},"networking",{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/opencode",{"id":647,"data":649,"body":652,"digest":653,"rendered":654},{"title":650,"description":651},"OpenCode Agent","Run the OpenCode agent in a session using an Anthropic API key.","\nSpin up the OpenCode coding agent inside a VM session and prompt it with natural language. Reach for this when you want an autonomous coding agent that can read files, run commands, and follow project conventions — backed by your own Anthropic API key.\n\n## How it works\n\nRegister the `opencode` software with `agentOS({ software: [opencode] })` so the runtime knows the agent type. The client then calls `agent.createSession(\"opencode\", ...)`, passing `ANTHROPIC_API_KEY` through `env`, and drives the agent with `agent.sendPrompt`. The example also shows two extension points: drop a `SKILL.md` into `~/.config/opencode/skills/` before creating the session and the agent auto-discovers it, and wire in extra tools via `mcpServers` (local child-process or remote URL). Pre-install any `npx` MCP server first so install output does not corrupt the stdio handshake.\n\n## Run it\n\n```bash\nnpm install\nexport ANTHROPIC_API_KEY=sk-ant-...\nnpx tsx server.ts # starts the registry on http://localhost:6420\nnpx tsx client.ts # creates a session and prints the agent's reply\n```\n\nThe agent answers your prompt — e.g. listing the files in the working directory.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/opencode)\n","29e02aa02e906b38",{"html":655,"metadata":656},"\u003Cp>Spin up the OpenCode coding agent inside a VM session and prompt it with natural language. Reach for this when you want an autonomous coding agent that can read files, run commands, and follow project conventions — backed by your own Anthropic API key.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Register the \u003Ccode>opencode\u003C/code> software with \u003Ccode>agentOS({ software: [opencode] })\u003C/code> so the runtime knows the agent type. The client then calls \u003Ccode>agent.createSession(\"opencode\", ...)\u003C/code>, passing \u003Ccode>ANTHROPIC_API_KEY\u003C/code> through \u003Ccode>env\u003C/code>, and drives the agent with \u003Ccode>agent.sendPrompt\u003C/code>. The example also shows two extension points: drop a \u003Ccode>SKILL.md\u003C/code> into \u003Ccode>~/.config/opencode/skills/\u003C/code> before creating the session and the agent auto-discovers it, and wire in extra tools via \u003Ccode>mcpServers\u003C/code> (local child-process or remote URL). Pre-install any \u003Ccode>npx\u003C/code> MCP server first so install output does not corrupt the stdio handshake.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nexport ANTHROPIC_API_KEY=sk-ant-...\nnpx tsx server.ts # starts the registry on http://localhost:6420\nnpx tsx client.ts # creates a session and prints the agent's reply\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#FF8F40">export\u003C/span>\u003Cspan style="color:#BFBDB6"> ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#BFBDB6">sk-ant-...\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # starts the registry on http://localhost:6420\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # creates a session and prints the agent's reply\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nexport ANTHROPIC_API_KEY=sk-ant-...\nnpx tsx server.ts # starts the registry on http://localhost:6420\nnpx tsx client.ts # creates a session and prints the agent's reply\n\u003C/code>\u003C/pre>\n\u003Cp>The agent answers your prompt — e.g. listing the files in the working directory.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/opencode\">View source on GitHub\u003C/a>\u003C/p>",{"headings":657,"localImagePaths":661,"remoteImagePaths":662,"frontmatter":663},[658,659,660],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/permissions",{"id":664,"data":666,"body":668,"digest":669,"rendered":670},{"title":181,"description":667},"Apply permission policies: grant network, deny filesystem paths, and scope what the guest can do.","\nPermission policies decide what guest code is allowed to touch — the network, the filesystem, and named bindings. Reach for this when you need to hand untrusted or agent-generated code a VM that can only do exactly what you intend.\n\n## How it works\n\nEach policy is a small object passed to `agentOS({ permissions })`. A policy sets a `default` (`allow` or `deny`) and a list of `rules` that flip the decision for specific paths, hosts, or binding names. This example composes four policies and merges them into one permission set:\n\n- **Network** granted outright, with a stricter override that denies by default and allows only `api.example.com`.\n- **Filesystem** allowed by default but denied for anything under `/vault/**`.\n- **Bindings** denied by default, allowing only the `add` binding by name.\n\nRules are evaluated against the defaults, so you compose from broad posture down to narrow exceptions. The resulting VM enforces all of them on every guest operation.\n\n## Run it\n\n```sh\nnpm install\nnpx tsx server.ts\n```\n\nThe registry starts with a VM whose guest can reach `api.example.com`, cannot read `/vault`, and can only invoke the `add` binding.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/permissions)\n","dc539bf430153ff4",{"html":671,"metadata":672},"\u003Cp>Permission policies decide what guest code is allowed to touch — the network, the filesystem, and named bindings. Reach for this when you need to hand untrusted or agent-generated code a VM that can only do exactly what you intend.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Each policy is a small object passed to \u003Ccode>agentOS({ permissions })\u003C/code>. A policy sets a \u003Ccode>default\u003C/code> (\u003Ccode>allow\u003C/code> or \u003Ccode>deny\u003C/code>) and a list of \u003Ccode>rules\u003C/code> that flip the decision for specific paths, hosts, or binding names. This example composes four policies and merges them into one permission set:\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Network\u003C/strong> granted outright, with a stricter override that denies by default and allows only \u003Ccode>api.example.com\u003C/code>.\u003C/li>\n\u003Cli>\u003Cstrong>Filesystem\u003C/strong> allowed by default but denied for anything under \u003Ccode>/vault/**\u003C/code>.\u003C/li>\n\u003Cli>\u003Cstrong>Bindings\u003C/strong> denied by default, allowing only the \u003Ccode>add\u003C/code> binding by name.\u003C/li>\n\u003C/ul>\n\u003Cp>Rules are evaluated against the defaults, so you compose from broad posture down to narrow exceptions. The resulting VM enforces all of them on every guest operation.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpx tsx server.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpx tsx server.ts\n\u003C/code>\u003C/pre>\n\u003Cp>The registry starts with a VM whose guest can reach \u003Ccode>api.example.com\u003C/code>, cannot read \u003Ccode>/vault\u003C/code>, and can only invoke the \u003Ccode>add\u003C/code> binding.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/permissions\">View source on GitHub\u003C/a>\u003C/p>",{"headings":673,"localImagePaths":677,"remoteImagePaths":678,"frontmatter":679},[674,675,676],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/persistence",{"id":680,"data":682,"body":685,"digest":686,"rendered":687},{"title":683,"description":684},"Persistence","Session persistence: lifecycle management and resuming a session after disconnect.","\nVMs sleep when idle and wake on demand, so sessions outlive any single connection. Reach for this when an agent needs to survive client disconnects, restarts, or long gaps between turns without losing its transcript.\n\n## How it works\n\nThe server registers a VM with `agentOS({ software: [pi] })` and `setup`. On the client, `connect()` surfaces `vmBooted` and `vmShutdown` lifecycle events — the shutdown payload's `reason` (`\"sleep\"`, `\"destroy\"`, or `\"error\"`) tells you why the VM stopped. Sessions are written to durable storage as they run, so even with no VM running you can call `vm.listPersistedSessions()` to enumerate past sessions and `vm.getSessionEvents(sessionId)` to replay a session's ordered event transcript after a disconnect.\n\n## Run it\n\n```sh\nnpm install\nnpx tsx examples/persistence/server.ts # terminal 1: start the registry\nnpx tsx examples/persistence/lifecycle-client.ts # terminal 2: watch boot/shutdown events\nnpx tsx examples/persistence/resume-client.ts # later: list and replay persisted sessions\n```\n\nThe lifecycle client logs `VM is ready` then shutdown reasons; the resume client prints prior session counts and replays the latest transcript.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/persistence)\n","3c8f8f5aa6d86cb5",{"html":688,"metadata":689},"\u003Cp>VMs sleep when idle and wake on demand, so sessions outlive any single connection. Reach for this when an agent needs to survive client disconnects, restarts, or long gaps between turns without losing its transcript.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The server registers a VM with \u003Ccode>agentOS({ software: [pi] })\u003C/code> and \u003Ccode>setup\u003C/code>. On the client, \u003Ccode>connect()\u003C/code> surfaces \u003Ccode>vmBooted\u003C/code> and \u003Ccode>vmShutdown\u003C/code> lifecycle events — the shutdown payload’s \u003Ccode>reason\u003C/code> (\u003Ccode>\"sleep\"\u003C/code>, \u003Ccode>\"destroy\"\u003C/code>, or \u003Ccode>\"error\"\u003C/code>) tells you why the VM stopped. Sessions are written to durable storage as they run, so even with no VM running you can call \u003Ccode>vm.listPersistedSessions()\u003C/code> to enumerate past sessions and \u003Ccode>vm.getSessionEvents(sessionId)\u003C/code> to replay a session’s ordered event transcript after a disconnect.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpx tsx examples/persistence/server.ts # terminal 1: start the registry\nnpx tsx examples/persistence/lifecycle-client.ts # terminal 2: watch boot/shutdown events\nnpx tsx examples/persistence/resume-client.ts # later: list and replay persisted sessions\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> examples/persistence/server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # terminal 1: start the registry\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> examples/persistence/lifecycle-client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # terminal 2: watch boot/shutdown events\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> examples/persistence/resume-client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # later: list and replay persisted sessions\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpx tsx examples/persistence/server.ts # terminal 1: start the registry\nnpx tsx examples/persistence/lifecycle-client.ts # terminal 2: watch boot/shutdown events\nnpx tsx examples/persistence/resume-client.ts # later: list and replay persisted sessions\n\u003C/code>\u003C/pre>\n\u003Cp>The lifecycle client logs \u003Ccode>VM is ready\u003C/code> then shutdown reasons; the resume client prints prior session counts and replays the latest transcript.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/persistence\">View source on GitHub\u003C/a>\u003C/p>",{"headings":690,"localImagePaths":694,"remoteImagePaths":695,"frontmatter":696},[691,692,693],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/pi",{"id":697,"data":699,"body":702,"digest":703,"rendered":704},{"title":700,"description":701},"Pi Agent","Run the Pi coding agent in a session, including quick start and session management.","\nSpin up the Pi coding agent inside a VM, open a session, and send it prompts. Reach for this when you want an end-to-end agent loop — quick start plus the session knobs for skills and MCP servers.\n\n## How it works\n\nThe server registers a VM with the `pi` software package and starts the registry. The client grabs a VM with `getOrCreate`, then `createSession(\"pi\", …)` passing the `ANTHROPIC_API_KEY` through `env`. From there `sendPrompt` runs a turn and returns the agent's `text`. Sessions are configurable: drop a `SKILL.md` into the agent's skills directory (via `mkdir` + `writeFile`) before creating the session and it's auto-discovered, and pass `mcpServers` (local child-process or remote URL) to expose extra tools. Pre-install any `npx`-launched MCP server so install output doesn't corrupt the stdio handshake.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # then run the client in another shell\n```\n\nThe agent answers the prompt and prints its response to the console.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/pi)\n","4695a9d363523cb5",{"html":705,"metadata":706},"\u003Cp>Spin up the Pi coding agent inside a VM, open a session, and send it prompts. Reach for this when you want an end-to-end agent loop — quick start plus the session knobs for skills and MCP servers.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The server registers a VM with the \u003Ccode>pi\u003C/code> software package and starts the registry. The client grabs a VM with \u003Ccode>getOrCreate\u003C/code>, then \u003Ccode>createSession(\"pi\", …)\u003C/code> passing the \u003Ccode>ANTHROPIC_API_KEY\u003C/code> through \u003Ccode>env\u003C/code>. From there \u003Ccode>sendPrompt\u003C/code> runs a turn and returns the agent’s \u003Ccode>text\u003C/code>. Sessions are configurable: drop a \u003Ccode>SKILL.md\u003C/code> into the agent’s skills directory (via \u003Ccode>mkdir\u003C/code> + \u003Ccode>writeFile\u003C/code>) before creating the session and it’s auto-discovered, and pass \u003Ccode>mcpServers\u003C/code> (local child-process or remote URL) to expose extra tools. Pre-install any \u003Ccode>npx\u003C/code>-launched MCP server so install output doesn’t corrupt the stdio handshake.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # then run the client in another shell\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # then run the client in another shell\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # then run the client in another shell\n\u003C/code>\u003C/pre>\n\u003Cp>The agent answers the prompt and prints its response to the console.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/pi\">View source on GitHub\u003C/a>\u003C/p>",{"headings":707,"localImagePaths":711,"remoteImagePaths":712,"frontmatter":713},[708,709,710],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/processes",{"id":714,"data":716,"body":718,"digest":719,"rendered":720},{"title":389,"description":717},"Process management inside the VM: exec, spawn, stdin, lifecycle, shell sessions, process events, and visibility.","\nRun commands and long-lived processes inside a VM, stream their output, and drive interactive shells. Reach for this whenever an agent needs to invoke tools, start a dev server, pipe data over stdin, or attach a terminal.\n\n## How it works\n\nA `server.ts` registers a VM with its software, and each script connects with `createClient` and grabs a VM via `client.vm.getOrCreate(\"my-agent\")`. From there the VM handle exposes the full process surface:\n\n- **`exec`** — run a command to completion and collect `stdout`, `stderr`, and `exitCode` in one call.\n- **`spawn` + lifecycle** — start a process for a `pid`, then `listProcesses`, `getProcess`, `waitProcess`, `stopProcess` (SIGTERM), and `killProcess` (SIGKILL).\n- **stdin** — `writeProcessStdin` and `closeProcessStdin` to feed a running process, including an interactive `sh` (see `shell.ts`).\n- **events** — `agent.connect()` returns a connection that emits `processOutput` / `processExit` and `shellData`, so output streams live as it is produced.\n\n`visibility.ts` shows how to enumerate and inspect everything running in the VM.\n\n## Run it\n\n```bash\nnpm install\nnpx tsx server.ts & # start the VM registry on :6420\nnpx tsx exec.ts # then run any of the scripts (spawn.ts, stdin.ts, shell.ts, ...)\n```\n\nEach script prints its process output and exit codes to the console.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/processes)\n","5d926edd519f9a13",{"html":721,"metadata":722},"\u003Cp>Run commands and long-lived processes inside a VM, stream their output, and drive interactive shells. Reach for this whenever an agent needs to invoke tools, start a dev server, pipe data over stdin, or attach a terminal.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A \u003Ccode>server.ts\u003C/code> registers a VM with its software, and each script connects with \u003Ccode>createClient\u003C/code> and grabs a VM via \u003Ccode>client.vm.getOrCreate(\"my-agent\")\u003C/code>. From there the VM handle exposes the full process surface:\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>\u003Ccode>exec\u003C/code>\u003C/strong> — run a command to completion and collect \u003Ccode>stdout\u003C/code>, \u003Ccode>stderr\u003C/code>, and \u003Ccode>exitCode\u003C/code> in one call.\u003C/li>\n\u003Cli>\u003Cstrong>\u003Ccode>spawn\u003C/code> + lifecycle\u003C/strong> — start a process for a \u003Ccode>pid\u003C/code>, then \u003Ccode>listProcesses\u003C/code>, \u003Ccode>getProcess\u003C/code>, \u003Ccode>waitProcess\u003C/code>, \u003Ccode>stopProcess\u003C/code> (SIGTERM), and \u003Ccode>killProcess\u003C/code> (SIGKILL).\u003C/li>\n\u003Cli>\u003Cstrong>stdin\u003C/strong> — \u003Ccode>writeProcessStdin\u003C/code> and \u003Ccode>closeProcessStdin\u003C/code> to feed a running process, including an interactive \u003Ccode>sh\u003C/code> (see \u003Ccode>shell.ts\u003C/code>).\u003C/li>\n\u003Cli>\u003Cstrong>events\u003C/strong> — \u003Ccode>agent.connect()\u003C/code> returns a connection that emits \u003Ccode>processOutput\u003C/code> / \u003Ccode>processExit\u003C/code> and \u003Ccode>shellData\u003C/code>, so output streams live as it is produced.\u003C/li>\n\u003C/ul>\n\u003Cp>\u003Ccode>visibility.ts\u003C/code> shows how to enumerate and inspect everything running in the VM.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nnpx tsx server.ts & # start the VM registry on :6420\nnpx tsx exec.ts # then run any of the scripts (spawn.ts, stdin.ts, shell.ts, ...)\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#BFBDB6B3"> &#x26;\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the VM registry on :6420\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> exec.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # then run any of the scripts (spawn.ts, stdin.ts, shell.ts, ...)\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nnpx tsx server.ts & # start the VM registry on :6420\nnpx tsx exec.ts # then run any of the scripts (spawn.ts, stdin.ts, shell.ts, ...)\n\u003C/code>\u003C/pre>\n\u003Cp>Each script prints its process output and exit codes to the console.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/processes\">View source on GitHub\u003C/a>\u003C/p>",{"headings":723,"localImagePaths":727,"remoteImagePaths":728,"frontmatter":729},[724,725,726],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/queues",{"id":730,"data":732,"body":735,"digest":736,"rendered":737},{"title":733,"description":734},"Queues","Process agent tasks one at a time through a RivetKit queue, with ingest and review pipelines.","\nRun agent work through a durable queue so tasks are handled one at a time instead of all at once. Reach for this when prompts arrive faster than agents should process them — webhook bursts, batch jobs, or any workload where serialized, back-pressured execution beats parallel chaos.\n\n## How it works\n\nA RivetKit `actor` declares a `queue` and drains it inside its `run` loop with `c.queue.iter()`, processing each message sequentially. For every message the actor opens an Agent OS session against a shared VM, sends the prompt, and closes the session — so only one task runs at a time per actor.\n\nThe example shows three patterns over the same primitive:\n\n- **Basic** (`server.ts` / `client.ts`) — clients `send` prompts onto the queue; the runner processes them in order.\n- **Ingest** (`ingest-server.ts` / `ingest-client.ts`) — an HTTP action `push`es webhook payloads onto the queue for decoupled intake.\n- **Review** (`review-server.ts` / `review-client.ts`) — a completable queue (`iter({ completable: true })`) where the client `send`s with `{ wait: true }` and blocks for the agent's returned summary.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # in one terminal\nnpx tsx client.ts # in another\n```\n\nTasks queue up and the agent works through them one at a time; swap in `ingest-*` or `review-*` to try the other pipelines.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/queues)\n","d8d2a105c20fc691",{"html":738,"metadata":739},"\u003Cp>Run agent work through a durable queue so tasks are handled one at a time instead of all at once. Reach for this when prompts arrive faster than agents should process them — webhook bursts, batch jobs, or any workload where serialized, back-pressured execution beats parallel chaos.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A RivetKit \u003Ccode>actor\u003C/code> declares a \u003Ccode>queue\u003C/code> and drains it inside its \u003Ccode>run\u003C/code> loop with \u003Ccode>c.queue.iter()\u003C/code>, processing each message sequentially. For every message the actor opens an Agent OS session against a shared VM, sends the prompt, and closes the session — so only one task runs at a time per actor.\u003C/p>\n\u003Cp>The example shows three patterns over the same primitive:\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Basic\u003C/strong> (\u003Ccode>server.ts\u003C/code> / \u003Ccode>client.ts\u003C/code>) — clients \u003Ccode>send\u003C/code> prompts onto the queue; the runner processes them in order.\u003C/li>\n\u003Cli>\u003Cstrong>Ingest\u003C/strong> (\u003Ccode>ingest-server.ts\u003C/code> / \u003Ccode>ingest-client.ts\u003C/code>) — an HTTP action \u003Ccode>push\u003C/code>es webhook payloads onto the queue for decoupled intake.\u003C/li>\n\u003Cli>\u003Cstrong>Review\u003C/strong> (\u003Ccode>review-server.ts\u003C/code> / \u003Ccode>review-client.ts\u003C/code>) — a completable queue (\u003Ccode>iter({ completable: true })\u003C/code>) where the client \u003Ccode>send\u003C/code>s with \u003Ccode>{ wait: true }\u003C/code> and blocks for the agent’s returned summary.\u003C/li>\n\u003C/ul>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # in one terminal\nnpx tsx client.ts # in another\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in one terminal\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in another\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # in one terminal\nnpx tsx client.ts # in another\n\u003C/code>\u003C/pre>\n\u003Cp>Tasks queue up and the agent works through them one at a time; swap in \u003Ccode>ingest-*\u003C/code> or \u003Ccode>review-*\u003C/code> to try the other pipelines.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/queues\">View source on GitHub\u003C/a>\u003C/p>",{"headings":740,"localImagePaths":744,"remoteImagePaths":745,"frontmatter":746},[741,742,743],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/quickstart-app",{"id":747,"data":749,"body":752,"digest":753,"rendered":754},{"title":750,"description":751},"Quickstart App","Full RivetKit app: an agentOS server registry plus a client that streams session events.","\nA complete starting point that wires an agentOS server to a client. Reach for this when you want the whole loop in one place: a server that registers a VM with agent software, and a client that opens a session, sends a prompt, and streams the agent's events back.\n\n## How it works\n\n`server.ts` builds a VM with `agentOS({ software: [pi] })`, registers it via `setup`, and starts the RivetKit registry. The client connects to that registry, calls `getOrCreate` to obtain a VM handle, and subscribes to `sessionEvent` over a live connection. It then creates a `pi` session (passing the Anthropic API key through `env`), sends a prompt, and reads back the file the agent wrote to `/workspace`. An `Agent.tsx` component shows the same flow from React, streaming events into component state with `useEvent`.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the registry\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # in another shell, drive a session\n```\n\nThe client prints streamed session events and the contents of the `hello.js` file the agent creates.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/quickstart-app)\n","1a00ecfd763c3dec",{"html":755,"metadata":756},"\u003Cp>A complete starting point that wires an agentOS server to a client. Reach for this when you want the whole loop in one place: a server that registers a VM with agent software, and a client that opens a session, sends a prompt, and streams the agent’s events back.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>\u003Ccode>server.ts\u003C/code> builds a VM with \u003Ccode>agentOS({ software: [pi] })\u003C/code>, registers it via \u003Ccode>setup\u003C/code>, and starts the RivetKit registry. The client connects to that registry, calls \u003Ccode>getOrCreate\u003C/code> to obtain a VM handle, and subscribes to \u003Ccode>sessionEvent\u003C/code> over a live connection. It then creates a \u003Ccode>pi\u003C/code> session (passing the Anthropic API key through \u003Ccode>env\u003C/code>), sends a prompt, and reads back the file the agent wrote to \u003Ccode>/workspace\u003C/code>. An \u003Ccode>Agent.tsx\u003C/code> component shows the same flow from React, streaming events into component state with \u003Ccode>useEvent\u003C/code>.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the registry\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # in another shell, drive a session\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the registry\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in another shell, drive a session\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the registry\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # in another shell, drive a session\n\u003C/code>\u003C/pre>\n\u003Cp>The client prints streamed session events and the contents of the \u003Ccode>hello.js\u003C/code> file the agent creates.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/quickstart-app\">View source on GitHub\u003C/a>\u003C/p>",{"headings":757,"localImagePaths":761,"remoteImagePaths":762,"frontmatter":763},[758,759,760],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/resource-limits",{"id":764,"data":766,"body":768,"digest":769,"rendered":770},{"title":221,"description":767},"Configure VM resource limits: processes, file descriptors, sockets, filesystem bytes, and WASM stack.","\nCap how much of the host a VM can consume. Reach for this when you run untrusted or agent-generated code and need hard ceilings on processes, file descriptors, sockets, filesystem storage, and WASM stack depth.\n\n## How it works\n\nThe VM accepts a `limits.resources` block when you call `agentOS({ ... })`. Each field bounds one kind of resource the guest can hold open at once: `maxProcesses`, `maxOpenFds`, `maxSockets`, a `maxFilesystemBytes` storage budget for the VFS, and a `maxWasmStackBytes` ceiling on the WASM call stack. The sidecar enforces these against the executor, so a guest that tries to exceed a cap is denied rather than allowed to exhaust the shared process. Defaults are bounded already; these values raise or lower them to fit your workload.\n\n## Run it\n\n```sh\nnpm install\nnpx tsx server.ts\n```\n\nThis starts a registry whose VM is provisioned with the configured resource caps.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/resource-limits)\n","6372174ca9e86a34",{"html":771,"metadata":772},"\u003Cp>Cap how much of the host a VM can consume. Reach for this when you run untrusted or agent-generated code and need hard ceilings on processes, file descriptors, sockets, filesystem storage, and WASM stack depth.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The VM accepts a \u003Ccode>limits.resources\u003C/code> block when you call \u003Ccode>agentOS({ ... })\u003C/code>. Each field bounds one kind of resource the guest can hold open at once: \u003Ccode>maxProcesses\u003C/code>, \u003Ccode>maxOpenFds\u003C/code>, \u003Ccode>maxSockets\u003C/code>, a \u003Ccode>maxFilesystemBytes\u003C/code> storage budget for the VFS, and a \u003Ccode>maxWasmStackBytes\u003C/code> ceiling on the WASM call stack. The sidecar enforces these against the executor, so a guest that tries to exceed a cap is denied rather than allowed to exhaust the shared process. Defaults are bounded already; these values raise or lower them to fit your workload.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpx tsx server.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpx tsx server.ts\n\u003C/code>\u003C/pre>\n\u003Cp>This starts a registry whose VM is provisioned with the configured resource caps.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/resource-limits\">View source on GitHub\u003C/a>\u003C/p>",{"headings":773,"localImagePaths":777,"remoteImagePaths":778,"frontmatter":779},[774,775,776],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/sandbox",{"id":780,"data":782,"body":785,"digest":786,"rendered":787},{"title":783,"description":784},"Sandbox","Mount a Sandbox Agent (Docker) filesystem into the VM and expose its process management as bindings.","\nBack a VM with a real Sandbox Agent container: the sandbox's filesystem appears as a mount inside the VM, and its process management is callable as bindings. Reach for this when you want guest code to read, write, and run against a live Docker sandbox instead of the in-memory VFS.\n\n## How it works\n\nThe server starts a sandbox through `SandboxAgent.start({ sandbox: docker() })`, then wires it into `agentOS` two ways. `createSandboxFs({ client })` returns a mount-plugin descriptor that projects the sandbox filesystem under `/home/agentos/sandbox`, so `vm.writeFile` and `vm.exec` operate on real container files. `createSandboxBindings({ client })` exposes the sandbox's process management as bindings, surfaced inside the VM as the `agentos-sandbox` CLI command. From the client you write a file to the mount, `exec` it, invoke a binding like `run-command`, and `spawn` a long-running process whose stdout/stderr stream back over `vm.connect()`.\n\n## Run it\n\n```sh\nnpm install\nnpm run server # starts the VM with the sandbox mount + bindings\nnpm run client # writes a file, runs it, and streams process output\n```\n\nYou should see `hello` printed from a file executed inside the Docker sandbox, followed by streamed output from the spawned dev process.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/sandbox)\n","4c5dab6eacfc04d1",{"html":788,"metadata":789},"\u003Cp>Back a VM with a real Sandbox Agent container: the sandbox’s filesystem appears as a mount inside the VM, and its process management is callable as bindings. Reach for this when you want guest code to read, write, and run against a live Docker sandbox instead of the in-memory VFS.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The server starts a sandbox through \u003Ccode>SandboxAgent.start({ sandbox: docker() })\u003C/code>, then wires it into \u003Ccode>agentOS\u003C/code> two ways. \u003Ccode>createSandboxFs({ client })\u003C/code> returns a mount-plugin descriptor that projects the sandbox filesystem under \u003Ccode>/home/agentos/sandbox\u003C/code>, so \u003Ccode>vm.writeFile\u003C/code> and \u003Ccode>vm.exec\u003C/code> operate on real container files. \u003Ccode>createSandboxBindings({ client })\u003C/code> exposes the sandbox’s process management as bindings, surfaced inside the VM as the \u003Ccode>agentos-sandbox\u003C/code> CLI command. From the client you write a file to the mount, \u003Ccode>exec\u003C/code> it, invoke a binding like \u003Ccode>run-command\u003C/code>, and \u003Ccode>spawn\u003C/code> a long-running process whose stdout/stderr stream back over \u003Ccode>vm.connect()\u003C/code>.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpm run server # starts the VM with the sandbox mount + bindings\nnpm run client # writes a file, runs it, and streams process output\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> run\u003C/span>\u003Cspan style="color:#AAD94C"> server\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # starts the VM with the sandbox mount + bindings\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> run\u003C/span>\u003Cspan style="color:#AAD94C"> client\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # writes a file, runs it, and streams process output\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpm run server # starts the VM with the sandbox mount + bindings\nnpm run client # writes a file, runs it, and streams process output\n\u003C/code>\u003C/pre>\n\u003Cp>You should see \u003Ccode>hello\u003C/code> printed from a file executed inside the Docker sandbox, followed by streamed output from the spawned dev process.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/sandbox\">View source on GitHub\u003C/a>\u003C/p>",{"headings":790,"localImagePaths":794,"remoteImagePaths":795,"frontmatter":796},[791,792,793],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/sessions",{"id":797,"data":799,"body":801,"digest":802,"rendered":803},{"title":245,"description":800},"Create, manage, and stream agent sessions over the RivetKit actor client.","\nSpin up an agent VM, open sessions against it, and drive them end to end: send prompts, stream responses, switch models, replay history, and tear sessions down. Reach for this when you need full lifecycle control over an agent rather than a one-shot prompt.\n\n## How it works\n\nThe server registers an agent VM with `agentOS({ software: [pi] })` and exposes it through a typed RivetKit `setup` registry. The client connects with `createClient` and grabs a VM handle with `getOrCreate`. From that handle you call `createSession` (with options like `env`, `cwd`, `mcpServers`, and `additionalInstructions`), then `sendPrompt`/`cancelPrompt` to run work. A `connect()` connection surfaces `sessionEvent`, `vmBooted`, and `vmShutdown` events for live streaming — subscribe before triggering actions so nothing is missed. Runtime knobs (`setModel`, `setMode`, `setThoughtLevel`), event replay (`getSessionEvents`, `getSequencedEvents`), persisted history, and multi-session fan-out within one VM round out the surface.\n\n## Run it\n\n```bash\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the registry\n# in another shell: drive the client functions\nANTHROPIC_API_KEY=sk-... npx tsx client.ts\n```\n\nThe server boots the agent VM and the client opens sessions, streams events, and prints session IDs and responses.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/sessions)\n","bd355ad1b6089ca5",{"html":804,"metadata":805},"\u003Cp>Spin up an agent VM, open sessions against it, and drive them end to end: send prompts, stream responses, switch models, replay history, and tear sessions down. Reach for this when you need full lifecycle control over an agent rather than a one-shot prompt.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The server registers an agent VM with \u003Ccode>agentOS({ software: [pi] })\u003C/code> and exposes it through a typed RivetKit \u003Ccode>setup\u003C/code> registry. The client connects with \u003Ccode>createClient\u003C/code> and grabs a VM handle with \u003Ccode>getOrCreate\u003C/code>. From that handle you call \u003Ccode>createSession\u003C/code> (with options like \u003Ccode>env\u003C/code>, \u003Ccode>cwd\u003C/code>, \u003Ccode>mcpServers\u003C/code>, and \u003Ccode>additionalInstructions\u003C/code>), then \u003Ccode>sendPrompt\u003C/code>/\u003Ccode>cancelPrompt\u003C/code> to run work. A \u003Ccode>connect()\u003C/code> connection surfaces \u003Ccode>sessionEvent\u003C/code>, \u003Ccode>vmBooted\u003C/code>, and \u003Ccode>vmShutdown\u003C/code> events for live streaming — subscribe before triggering actions so nothing is missed. Runtime knobs (\u003Ccode>setModel\u003C/code>, \u003Ccode>setMode\u003C/code>, \u003Ccode>setThoughtLevel\u003C/code>), event replay (\u003Ccode>getSessionEvents\u003C/code>, \u003Ccode>getSequencedEvents\u003C/code>), persisted history, and multi-session fan-out within one VM round out the surface.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the registry\n# in another shell: drive the client functions\nANTHROPIC_API_KEY=sk-... npx tsx client.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the registry\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># in another shell: drive the client functions\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the registry\n# in another shell: drive the client functions\nANTHROPIC_API_KEY=sk-... npx tsx client.ts\n\u003C/code>\u003C/pre>\n\u003Cp>The server boots the agent VM and the client opens sessions, streams events, and prints session IDs and responses.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/sessions\">View source on GitHub\u003C/a>\u003C/p>",{"headings":806,"localImagePaths":810,"remoteImagePaths":811,"frontmatter":812},[807,808,809],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/software",{"id":813,"data":815,"body":817,"digest":818,"rendered":819},{"title":253,"description":816},"Declare which software packages and CLI commands are available inside the VM.","\nThe commands an agent can run are determined by the software you install into its VM. This example declares a software set so a shell pipeline like `echo hello | grep hello` resolves inside the sandbox.\n\n## How it works\n\n`agentOS({ software: [...] })` takes a list of imported software packages, and together they define the CLI surface available to the guest. Common utilities — coreutils, sed, grep, gawk, findutils, diffutils, tar, and gzip — ship by default, so you only list the extras you need; here `pi` adds the agent itself. The client then runs commands through the VM via `exec`, which only succeed when the underlying binaries are present in the declared software set.\n\n## Run it\n\n```sh\nnpm install\nnpm run server # starts the registry on http://localhost:6420\nnpm run client # runs \"echo hello | grep hello\" in the VM, prints \"hello\"\n```\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/software)\n","3ab6bf586094ee8b",{"html":820,"metadata":821},"\u003Cp>The commands an agent can run are determined by the software you install into its VM. This example declares a software set so a shell pipeline like \u003Ccode>echo hello | grep hello\u003C/code> resolves inside the sandbox.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>\u003Ccode>agentOS({ software: [...] })\u003C/code> takes a list of imported software packages, and together they define the CLI surface available to the guest. Common utilities — coreutils, sed, grep, gawk, findutils, diffutils, tar, and gzip — ship by default, so you only list the extras you need; here \u003Ccode>pi\u003C/code> adds the agent itself. The client then runs commands through the VM via \u003Ccode>exec\u003C/code>, which only succeed when the underlying binaries are present in the declared software set.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpm run server # starts the registry on http://localhost:6420\nnpm run client # runs "echo hello | grep hello" in the VM, prints "hello"\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> run\u003C/span>\u003Cspan style="color:#AAD94C"> server\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # starts the registry on http://localhost:6420\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> run\u003C/span>\u003Cspan style="color:#AAD94C"> client\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # runs "echo hello | grep hello" in the VM, prints "hello"\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpm run server # starts the registry on http://localhost:6420\nnpm run client # runs \"echo hello | grep hello\" in the VM, prints \"hello\"\n\u003C/code>\u003C/pre>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/software\">View source on GitHub\u003C/a>\u003C/p>",{"headings":822,"localImagePaths":826,"remoteImagePaths":827,"frontmatter":828},[823,824,825],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/webhooks",{"id":829,"data":831,"body":833,"digest":834,"rendered":835},{"title":269,"description":832},"Receive inbound webhooks (e.g. Slack) over Hono and dispatch them to an agent through a queue.","\nWire an external service's webhooks into an agent. Reach for this when a third party (Slack, GitHub, Stripe) POSTs events to you and you want an agent to react — without blocking the webhook response on the agent's work.\n\n## How it works\n\nA small [Hono](https://hono.dev) server exposes a `/slack/events` endpoint that handles Slack's URL verification handshake and then enqueues each inbound message onto a RivetKit `queue`. A `slackWorker` actor drains that queue, and for every message it spins up an Agent OS session, prompts the agent with the message text, and posts the reply back to Slack via the chat API. Decoupling the HTTP handler from the worker keeps webhook responses fast and lets agent runs proceed asynchronously.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-... SLACK_BOT_TOKEN=xoxb-... npx tsx server.ts\n```\n\nThe server listens for Slack events; each incoming message is queued, answered by the agent, and replied to in-channel.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/webhooks)\n","3a782c0e6033e24d",{"html":836,"metadata":837},"\u003Cp>Wire an external service’s webhooks into an agent. Reach for this when a third party (Slack, GitHub, Stripe) POSTs events to you and you want an agent to react — without blocking the webhook response on the agent’s work.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A small \u003Ca href=\"https://hono.dev\">Hono\u003C/a> server exposes a \u003Ccode>/slack/events\u003C/code> endpoint that handles Slack’s URL verification handshake and then enqueues each inbound message onto a RivetKit \u003Ccode>queue\u003C/code>. A \u003Ccode>slackWorker\u003C/code> actor drains that queue, and for every message it spins up an Agent OS session, prompts the agent with the message text, and posts the reply back to Slack via the chat API. Decoupling the HTTP handler from the worker keeps webhook responses fast and lets agent runs proceed asynchronously.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-... SLACK_BOT_TOKEN=xoxb-... npx tsx server.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#BFBDB6"> SLACK_BOT_TOKEN\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">xoxb-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-... SLACK_BOT_TOKEN=xoxb-... npx tsx server.ts\n\u003C/code>\u003C/pre>\n\u003Cp>The server listens for Slack events; each incoming message is queued, answered by the agent, and replied to in-channel.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/webhooks\">View source on GitHub\u003C/a>\u003C/p>",{"headings":838,"localImagePaths":842,"remoteImagePaths":843,"frontmatter":844},[839,840,841],{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks/workflows",{"id":845,"data":847,"body":850,"digest":851,"rendered":852},{"title":848,"description":849},"Workflows","Durable multi-step workflows that drive a VM across restarts, chaining each step's output into the next.","\n# Workflows\n\nRun multi-step agent work that survives crashes and restarts. Reach for this when a task has distinct stages — clone, fix, test, record — and you want each stage to be durable, retryable, and resumable rather than a single fragile call.\n\n## How it works\n\nA RivetKit `actor` whose `run` handler is built with `workflow()` orchestrates the steps, while a separate `agentOS` VM actor does the actual work over the client. Each `ctx.step(...)` is recorded, retried, and resumed independently: if the process crashes mid-run, replay skips completed steps and continues from where it left off. The orchestrator loops on a durable `queue`, waiting for the next request, then runs its steps in order against the VM. Output flows step-to-step through return values and the VM filesystem — the bug-fixer chains clone -> fix -> test -> record, and the code-reviewer writes a review file in one agent session and feeds it into a second. Sessions are created and closed inside a step, so they never outlive the work they back.\n\n## Run it\n\n```bash\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the orchestrator + VM\nnpx tsx client.ts # trigger the durable bug-fix workflow\n```\n\nThe client sends a request to the workflow queue; the workflow drives the VM through each step and prints the last issue and test exit code.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/workflows)\n","a1a2e758a2472825",{"html":853,"metadata":854},"\u003Ch1 id=\"workflows\">Workflows\u003C/h1>\n\u003Cp>Run multi-step agent work that survives crashes and restarts. Reach for this when a task has distinct stages — clone, fix, test, record — and you want each stage to be durable, retryable, and resumable rather than a single fragile call.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A RivetKit \u003Ccode>actor\u003C/code> whose \u003Ccode>run\u003C/code> handler is built with \u003Ccode>workflow()\u003C/code> orchestrates the steps, while a separate \u003Ccode>agentOS\u003C/code> VM actor does the actual work over the client. Each \u003Ccode>ctx.step(...)\u003C/code> is recorded, retried, and resumed independently: if the process crashes mid-run, replay skips completed steps and continues from where it left off. The orchestrator loops on a durable \u003Ccode>queue\u003C/code>, waiting for the next request, then runs its steps in order against the VM. Output flows step-to-step through return values and the VM filesystem — the bug-fixer chains clone -> fix -> test -> record, and the code-reviewer writes a review file in one agent session and feeds it into a second. Sessions are created and closed inside a step, so they never outlive the work they back.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the orchestrator + VM\nnpx tsx client.ts # trigger the durable bug-fix workflow\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the orchestrator + VM\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # trigger the durable bug-fix workflow\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the orchestrator + VM\nnpx tsx client.ts # trigger the durable bug-fix workflow\n\u003C/code>\u003C/pre>\n\u003Cp>The client sends a request to the workflow queue; the workflow drives the VM through each step and prints the last issue and test exit code.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/workflows\">View source on GitHub\u003C/a>\u003C/p>",{"headings":855,"localImagePaths":861,"remoteImagePaths":862,"frontmatter":863},[856,858,859,860],{"depth":469,"slug":857,"text":848},"workflows",{"depth":430,"slug":431,"text":432},{"depth":430,"slug":434,"text":435},{"depth":430,"slug":437,"text":438},[],[],{},"cookbooks",{"id":864,"data":866,"body":869,"digest":870,"rendered":871},{"title":867,"description":868},"Cookbooks","Runnable agentOS examples.","agentOS cookbooks — runnable examples for every capability. Each page mirrors an example in the repo; follow the **View source on GitHub** link to run it.","4d08c63bfbca701b",{"html":872,"metadata":873},"\u003Cp>agentOS cookbooks — runnable examples for every capability. Each page mirrors an example in the repo; follow the \u003Cstrong>View source on GitHub\u003C/strong> link to run it.\u003C/p>",{"headings":874,"localImagePaths":875,"remoteImagePaths":876,"frontmatter":877},[],[],[],{},"docs/architecture/packages-and-command-resolution",{"id":878,"data":880,"body":883,"filePath":884,"digest":885,"deferredRender":16},{"title":881,"description":882,"skill":16},"Packages & Command Resolution","How software is packaged, linked, resolved, and executed in an agentOS VM: a package is a directory, resolution is a $PATH walk, and a file's header picks its runtime.","How a command name becomes a running program, and how the software that provides it\nis packaged and linked. Everything is real files under\n[`/opt/agentos`](/docs/architecture/filesystem) — there is no command registry; the\nfilesystem and `$PATH` are the only source of truth. For the host API that produces\npackages, see [Software Definition](/docs/custom-software/definition).\n\n## Overview\n\n\u003Csvg viewBox=\"0 0 760 470\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"From a command name to a running program: resolve over PATH, dispatch by header, run under the VM policy\" style=\"width:100%;max-width:680px;height:auto;font-family:system-ui,sans-serif\">\n \u003Cdefs>\n \u003Cmarker id=\"pcr-ah\" viewBox=\"0 0 10 10\" refX=\"8.5\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0 0 L10 5 L0 10 z\" fill=\"#64748b\"/>\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"280\" y=\"16\" width=\"200\" height=\"42\" rx=\"7\" fill=\"#eef2ff\" stroke=\"#6366f1\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"380\" y=\"42\" text-anchor=\"middle\" font-size=\"14\" fill=\"#1e1b4b\">exec \u003Ctspan font-family=\"ui-monospace,monospace\">\"pi\"\u003C/tspan>\u003C/text>\n \u003Cline x1=\"380\" y1=\"58\" x2=\"380\" y2=\"84\" stroke=\"#64748b\" stroke-width=\"1.5\" marker-end=\"url(#pcr-ah)\"/>\n \u003Crect x=\"265\" y=\"86\" width=\"230\" height=\"42\" rx=\"7\" fill=\"#f8fafc\" stroke=\"#94a3b8\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"380\" y=\"112\" text-anchor=\"middle\" font-size=\"13\" fill=\"#0f172a\">\u003Ctspan font-family=\"ui-monospace,monospace\">$PATH\u003C/tspan> walk over the VFS\u003C/text>\n \u003Cline x1=\"380\" y1=\"128\" x2=\"380\" y2=\"154\" stroke=\"#64748b\" stroke-width=\"1.5\" marker-end=\"url(#pcr-ah)\"/>\n \u003Crect x=\"235\" y=\"156\" width=\"290\" height=\"42\" rx=\"7\" fill=\"#f8fafc\" stroke=\"#94a3b8\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"380\" y=\"176\" text-anchor=\"middle\" font-size=\"12.5\" font-family=\"ui-monospace,monospace\" fill=\"#0f172a\">/opt/agentos/bin/pi\u003C/text>\n \u003Ctext x=\"380\" y=\"191\" text-anchor=\"middle\" font-size=\"10.5\" fill=\"#64748b\">a real symlink in the VFS\u003C/text>\n \u003Cline x1=\"380\" y1=\"198\" x2=\"380\" y2=\"224\" stroke=\"#64748b\" stroke-width=\"1.5\" marker-end=\"url(#pcr-ah)\"/>\n \u003Crect x=\"275\" y=\"226\" width=\"210\" height=\"42\" rx=\"7\" fill=\"#fef9c3\" stroke=\"#eab308\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"380\" y=\"252\" text-anchor=\"middle\" font-size=\"13\" fill=\"#422006\">read header (binfmt)\u003C/text>\n \u003Cline x1=\"380\" y1=\"268\" x2=\"102\" y2=\"316\" stroke=\"#64748b\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Cline x1=\"380\" y1=\"268\" x2=\"289\" y2=\"316\" stroke=\"#64748b\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Cline x1=\"380\" y1=\"268\" x2=\"476\" y2=\"316\" stroke=\"#64748b\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Cline x1=\"380\" y1=\"268\" x2=\"660\" y2=\"316\" stroke=\"#ef4444\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Crect x=\"18\" y=\"318\" width=\"168\" height=\"58\" rx=\"7\" fill=\"#ecfeff\" stroke=\"#06b6d4\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"102\" y=\"340\" text-anchor=\"middle\" font-size=\"11\" font-family=\"ui-monospace,monospace\" fill=\"#155e75\">#!…node\u003C/text>\n \u003Ctext x=\"102\" y=\"360\" text-anchor=\"middle\" font-size=\"12.5\" fill=\"#0e2a33\">JavaScript · V8\u003C/text>\n \u003Crect x=\"205\" y=\"318\" width=\"168\" height=\"58\" rx=\"7\" fill=\"#ecfeff\" stroke=\"#06b6d4\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"289\" y=\"340\" text-anchor=\"middle\" font-size=\"11\" font-family=\"ui-monospace,monospace\" fill=\"#155e75\">#!…python3\u003C/text>\n \u003Ctext x=\"289\" y=\"360\" text-anchor=\"middle\" font-size=\"12.5\" fill=\"#0e2a33\">Python · Pyodide\u003C/text>\n \u003Crect x=\"392\" y=\"318\" width=\"168\" height=\"58\" rx=\"7\" fill=\"#ecfeff\" stroke=\"#06b6d4\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"476\" y=\"340\" text-anchor=\"middle\" font-size=\"11\" font-family=\"ui-monospace,monospace\" fill=\"#155e75\">{'\\\\0asm'}\u003C/text>\n \u003Ctext x=\"476\" y=\"360\" text-anchor=\"middle\" font-size=\"12.5\" fill=\"#0e2a33\">WebAssembly\u003C/text>\n \u003Crect x=\"579\" y=\"318\" width=\"163\" height=\"58\" rx=\"7\" fill=\"#fee2e2\" stroke=\"#ef4444\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"660\" y=\"340\" text-anchor=\"middle\" font-size=\"10.5\" font-family=\"ui-monospace,monospace\" fill=\"#7f1d1d\">ELF / Mach-O / PE\u003C/text>\n \u003Ctext x=\"660\" y=\"360\" text-anchor=\"middle\" font-size=\"12.5\" fill=\"#7f1d1d\">ENOEXEC\u003C/text>\n \u003Cline x1=\"102\" y1=\"376\" x2=\"102\" y2=\"404\" stroke=\"#22c55e\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Cline x1=\"289\" y1=\"376\" x2=\"289\" y2=\"404\" stroke=\"#22c55e\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Cline x1=\"476\" y1=\"376\" x2=\"476\" y2=\"404\" stroke=\"#22c55e\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Crect x=\"18\" y=\"406\" width=\"542\" height=\"40\" rx=\"7\" fill=\"#f0fdf4\" stroke=\"#22c55e\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"289\" y=\"431\" text-anchor=\"middle\" font-size=\"12.5\" fill=\"#14532d\">spawn under the VM permission policy\u003C/text>\n\u003C/svg>\n\n- **Resolve** — a real `$PATH` walk over the VFS; the first executable match wins.\n- **Dispatch** — by the file's *header* (`binfmt`): a `#!` shebang or a magic number. Never the name, never the extension.\n- **Run** — on one of three runtimes: JavaScript (V8), WebAssembly, Python (Pyodide). See [Processes](/docs/architecture/processes).\n- **Confine** — every process runs under the VM's single [permission policy](/docs/security-model). No per-command tiers.\n\n## Packages\n\nA package is a directory; its metadata is a normal `package.json` (`name`, `version`,\nand a `bin` command map) plus a small `agentos-package.json` (the agentOS-specific\n`name`/`agent`/`provides`). The shipped package contains **real files** — it's a plain npm\ndependency. The `/opt/agentos/\u003Cname>/\u003Cversion>/` tree below, with its `bin/` symlink farm,\nis what the runtime **projects** from that package when it mounts it:\n\n```\n/opt/agentos/\u003Cname>/\u003Cversion>/\n├── package.json # name, version, and the \"bin\" map (command → entry file)\n├── agentos-package.json # agentOS metadata: name, optional agent block, provides\n├── bin/ # symlinks the PROJECTION builds from package.json \"bin\"\n│ ├── ls → ../libexec/coreutils # → multicall blob\n│ └── vdir → ../libexec/coreutils # an \"alias\" is just another symlink\n├── libexec/coreutils # helpers run by other programs, never on $PATH\n├── node_modules/ | lib/ # support payload (a JS CLI's flat, self-contained closure)\n└── share/man/man1/ls.1 # man pages and other FHS content\n/opt/agentos/\u003Cname>/current → \u003Cversion> # version pointer; upgrade re-points it (atomic rename)\n```\n\n| Path | Contents |\n|---|---|\n| `package.json` | `name`, `version`, and a `bin` map (command → entry file). |\n| `agentos-package.json` | agentOS metadata the sidecar reads on mount: `name`, an optional `agent` block, and any `provides` (files/env). Generated for command/WASM packages; carries the `agent` block for agents. |\n| `bin/` | Command symlinks the projection builds from `package.json` `bin`; each basename is the command name. (Not part of the shipped package — npm can't carry symlinks.) |\n| `libexec/` | Helpers invoked by other programs, never on `$PATH` (e.g. a multicall blob). |\n| `node_modules/`, `lib/` | Non-executable payload — bundled deps and assets. |\n| `share/` | FHS data — `share/man/man\u003Cn>/*`, etc. |\n| `current` | Symlink `→ \u003Cversion>`; switching versions is one atomic rename. |\n\n```jsonc\n// package.json — commands come from \"bin\"; an agent's ACP entrypoint is just one of them\n{ \"name\": \"pi\", \"version\": \"0.60.0\", \"bin\": { \"pi-acp\": \"dist/acp.js\" } }\n```\n\nA directory is a **valid package** when:\n\n- **Commands come from `package.json` `bin`** (command → a real entry file), and each entry\n **dispatches by header** — a magic number or `#!` shebang, no `.wasm`/`.js` extension or\n `runtime`/`type` field; a headerless entry is `ENOEXEC`. The package ships **no symlinks**\n (npm-safe); the runtime builds the `bin/` farm under `/opt/agentos` itself.\n- **Aliases are symlinks** in the projected `bin/` farm — several names for one program (or a\n multicall blob); `argv[0]` is the invoked name.\n- **It is self-contained** — every import/require/asset resolves inside the package; nothing\n comes from a host `node_modules`, pnpm store, or workspace at runtime\n ([packaging](/docs/custom-software/definition) flattens/bundles deps in).\n- **Minimal metadata** — `package.json` carries only the command set (`bin`) and `version`; there\n is no command list beyond `bin`, no permission tiers (the [VM policy](#confinement--trust)\n governs every command), and no dependency list. A small **`agentos-package.json`** alongside it\n holds the agentOS-specific fields the sidecar reads when it mounts the package — the `name`, an\n optional `agent` block, and any `provides` (files/env). The client never carries this on the\n wire; it forwards only the package directory.\n\n## Linking\n\nLinking is creating the `bin/` symlinks in a `$PATH` directory. agentOS follows Homebrew:\n`/opt/agentos/\u003Cname>` is the cellar, and every command is symlinked into one managed prefix,\n**`/opt/agentos/bin`**, which is on `$PATH`. The standard dirs (`/usr/bin`, `/usr/local/bin`,\n`/bin`) stay ordinary writable Linux dirs — agentOS never writes to them.\n\n\u003Csvg viewBox=\"0 0 760 150\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"PATH search order, left to right, first match wins\" style=\"width:100%;max-width:720px;height:auto;font-family:system-ui,sans-serif\">\n \u003Cdefs>\n \u003Cmarker id=\"pcr-ah2\" viewBox=\"0 0 10 10\" refX=\"8.5\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto\">\n \u003Cpath d=\"M0 0 L10 5 L0 10 z\" fill=\"#94a3b8\"/>\n \u003C/marker>\n \u003C/defs>\n \u003Ctext x=\"16\" y=\"20\" font-size=\"12\" fill=\"#0f172a\">Searched left → right — first match wins (left shadows right)\u003C/text>\n \u003Cline x1=\"16\" y1=\"33\" x2=\"744\" y2=\"33\" stroke=\"#cbd5e1\" stroke-width=\"1.2\" marker-end=\"url(#pcr-ah2)\"/>\n \u003Crect x=\"16\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" stroke-width=\"1.3\"/>\n \u003Ctext x=\"65.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#334155\">/usr/local/sbin\u003C/text>\n \u003Crect x=\"121\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" stroke-width=\"1.3\"/>\n \u003Ctext x=\"170.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#334155\">/usr/local/bin\u003C/text>\n \u003Crect x=\"226\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#eef2ff\" stroke=\"#6366f1\" stroke-width=\"2\"/>\n \u003Ctext x=\"275.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#3730a3\">/opt/agentos/bin\u003C/text>\n \u003Crect x=\"331\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" stroke-width=\"1.3\"/>\n \u003Ctext x=\"380.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#334155\">/usr/sbin\u003C/text>\n \u003Crect x=\"436\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" stroke-width=\"1.3\"/>\n \u003Ctext x=\"485.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#334155\">/usr/bin\u003C/text>\n \u003Crect x=\"541\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" stroke-width=\"1.3\"/>\n \u003Ctext x=\"590.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#334155\">/sbin\u003C/text>\n \u003Crect x=\"646\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" stroke-width=\"1.3\"/>\n \u003Ctext x=\"695.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#334155\">/bin\u003C/text>\n \u003Ctext x=\"16\" y=\"130\" font-size=\"11\" fill=\"#475569\">agentOS links into \u003Ctspan font-family=\"ui-monospace,monospace\" fill=\"#4338ca\">/opt/agentos/bin\u003C/tspan>; the rest are ordinary writable Linux dirs — drop a binary in \u003Ctspan font-family=\"ui-monospace,monospace\" fill=\"#4338ca\">/usr/local/bin\u003C/tspan> to shadow an agentOS tool.\u003C/text>\n\u003C/svg>\n\n| Software | Stored | Linked into |\n|---|---|---|\n| Base, mounted, and runtime-installed agentOS software | `/opt/agentos/\u003Cpkg>/\u003Cver>` (or the mount) | `/opt/agentos/bin` |\n| The user's own files | wherever they put them | `/usr/local/bin`, `/usr/bin`, … (normal) |\n\n- **Base & mounts** link into `/opt/agentos/bin` in a **read-only layer** projected from the\n host and shared across VMs — the symlinks are real but cost nothing per boot. A mounted host\n directory is linked the same way, with no copy.\n- **Runtime installs** add symlinks to `/opt/agentos/bin` in the **writable layer** via\n [`agentos-software link`](#the-agentos-software-cli) — ordinary symlinks, found by the normal walk.\n\n## Persistence\n\nLinks and installed files are **filesystem entries**, so they persist exactly when their\n[filesystem](/docs/architecture/filesystem) layer does — the same rule as VFS-persistent\n`pip`. A snapshotted/persistent volume keeps runtime installs and links across restart; an\nephemeral one drops them on teardown. There is no package-specific persistence mechanism.\n\n\u003CWarning>\nPersisting a layer an untrusted guest can write to also persists whatever the guest linked\nthere. Treat a guest-writable `/usr/local/bin` as guest-controlled on restore (see\n[Confinement & trust](#confinement--trust)).\n\u003C/Warning>\n\n## Execution dispatch (binfmt)\n\nA resolved file's leading bytes are read into a fixed buffer and dispatched like the Linux\nkernel's binary-format handlers. The command's **name plays no part** — `python3`, `node`,\nand `pi` are runtimes only by virtue of their files' headers.\n\n| Header | Result |\n|---|---|\n| `#!` at bytes 0–1 (`binfmt_script`) | the interpreter named on the line |\n| `\\0asm` (`00 61 73 6d`) | WebAssembly runtime |\n| `\\x7fELF` / Mach-O / PE | **`ENOEXEC`** — foreign binary format, no native-arch handler |\n| anything else | `ENOEXEC` (no implicit `/bin/sh` fallback here) |\n\nShebang handling matches `binfmt_script`:\n\n- The interpreter path is **literal and absolute** — not `$PATH`-searched. `#!/usr/bin/env node`\n works only because `/usr/bin/env` looks up its argument.\n- At most **one** argument follows, **not** whitespace-split (`#!/usr/bin/env node --flag` passes\n `node --flag` as a single arg).\n- The header read is bounded to a fixed buffer (`BINPRM_BUF_SIZE`); a longer line truncates.\n Interpreter chaining is depth-bounded (`ELOOP`); a missing interpreter is **`ENOENT`**, not `ENOEXEC`.\n\n\u003CNote>\n**Shell fallback.** On `ENOEXEC`, a POSIX shell re-runs a headerless script via `/bin/sh`. That\nretry lives in the shell ([agentos-shell](/docs/architecture/processes)), not the dispatcher,\nwhich stays strictly `binfmt`-faithful.\n\u003C/Note>\n\n### Multicall (busybox-style)\n\n`bin/ls → ../libexec/coreutils` resolves at open to the shared `coreutils` blob. `argv[0]` is\nthe caller's value **verbatim** (`\"ls\"`) — never derived from the symlink — and the blob selects\nits applet with `basename(argv[0])`, like busybox. Always invoke via the `bin/` name; calling the\nblob by its own path yields an `argv[0]` that selects no applet.\n\n## Command resolution\n\nA `$PATH` walk over the [VFS](/docs/architecture/filesystem), full Linux semantics:\n\n- A name **containing `/`** bypasses `$PATH` and resolves directly (relative to cwd, or absolute).\n- Otherwise each `:`-separated dir is searched in order; the first **executable** regular file\n wins (execute bit required — a non-executable match yields `EACCES`). Left shadows right.\n- An **empty `$PATH` element** (leading/trailing/`::`) means the **current working directory** —\n the POSIX footgun, kept for fidelity.\n- Matches are real VFS files/symlinks — `ls -l`-able, `stat`-able, removable, replaceable. The\n filesystem is authoritative; there is no resolution cache to grow stale.\n\n## The `agentos-software` CLI\n\n```\nagentos-software link \u003Cpath>\n```\n\n- `\u003Cpath>` is a package directory or a node module directory (its `package.json` `bin` map is\n the command list).\n- It brokers a request to the sidecar, which owns the filesystem; the CLI has no privilege of\n its own.\n- Linked names are validated (no `/`, `..`, control chars, overlong names), and for a\n guest-supplied package each symlink target must resolve inside the package root.\n\n## Confinement & trust\n\nEvery process runs under the VM's single [permission policy](/docs/security-model) — like a\nLinux process running with its user/namespace/container privileges, not privileges declared by\nthe binary. A package cannot grant itself permissions. The [trust boundary](/docs/security-model)\nis the sidecar (trusted) vs. the guest (untrusted):\n\n- **Linking changes discoverability, not privilege** — the policy is enforced at spawn,\n regardless of how a command was found.\n- **Shadowing is allowed, Linux-style** — a guest may drop a `node`/`ls` into a writable `$PATH`\n dir; trusted in-VM components defend by invoking tools via **absolute paths** (or a `$PATH`\n that excludes guest-writable dirs). The shadowing binary still runs only under the VM policy.\n- **Guest env is sanitized** like a privileged exec — `LD_*`, `DYLD_*`, `NODE_OPTIONS`, `PATH`,\n `BASH_ENV`, `*PRELOAD` are stripped, as glibc does under `AT_SECURE`.\n- **Trusted vs. guest packages** — symlink-escape checks apply only to guest-writable runtime packages.\n- **Bounded** — the runtime link count is bounded; it warns on approach and fails with a typed\n error naming the limit (see [Limits & Observability](/docs/architecture/limits-and-observability)).\n\n## See also\n\n- [Software Definition](/docs/custom-software/definition) — the host API that produces these packages.\n- [Processes](/docs/architecture/processes) — the JavaScript, WebAssembly, and Python runtimes.\n- [Filesystem](/docs/architecture/filesystem) — the VFS, layers, and persistence.\n- [Security Model](/docs/security-model) — the trust boundary and VM permission policy.","src/content/docs/docs/architecture/packages-and-command-resolution.mdx","de3879554bd53da6","docs/custom-software/publishing",{"id":886,"data":888,"body":891,"filePath":892,"digest":893,"deferredRender":16},{"title":889,"description":890},"Publishing Packages","Build, publish, and consume agentOS packages — locally, from npm, or from your own repo.","agentOS packages — WASM command sets and packed JS agents alike — go through one lifecycle, owned by the **`@rivet-dev/agentos-toolchain`** CLI. This page covers the full flow: building a package, publishing it to npm, and wiring a consumer at either a published version or a local checkout.\n\n## The lifecycle\n\nEvery package is an npm package whose default export points at a self-contained runtime dir (`dist/package/`) that the sidecar projects under `/opt/agentos/\u003Cname>/\u003Cversion>`. The toolchain provides four subcommands:\n\n| Command | What it does |\n|---|---|\n| `stage --commands-dir \u003Cdir>` | Populate `bin/` from a directory of compiled binaries, per the `commands` / `aliases` / `stubs` lists in the package's `agentos-package.json`. |\n| `build` | Assemble `dist/package/` from `bin/` (+ optional `share/`): a clean `package.json` with a `bin` command map, plus the runtime `agentos-package.json`. |\n| `pack` | Build a self-contained node-closure package from an npm package or local dir (JS agents; validates headers, rejects native addons). |\n| `publish` | Publish the built package to npm. Dist-tag is **`dev` by default**; the `latest` pointer only moves with an explicit `--latest`. |\n\n## Building\n\nIn the secure-exec registry, the `just` recipes drive the toolchain (see [Building Binaries](/docs/custom-software/building-wasm)):\n\n```bash\njust registry-native # compile the native wasm binaries (once per checkout)\njust registry-build # stage + assemble every registry package\njust registry-build coreutils # ... or one package\njust registry-status # inspect: version, staged bin/, assembled dist\n```\n\n## Publishing\n\nRegistry packages **version independently** — each package carries its own semver in its `package.json`. Bump and commit the version, then:\n\n```bash\njust registry-publish coreutils # publish under dist-tag `dev`\njust registry-publish coreutils my-branch # ... under a custom tag\njust registry-publish coreutils latest # DELIBERATE release: moves `latest`\njust registry-publish-all # every built software package, tag `dev`\n```\n\nConsumers installing `@agentos-software/\u003Cname>` with no tag resolve `latest`, so `latest` is reserved for deliberate releases — a dev publish can never clobber what users install.\n\n## Consuming published packages\n\nIn agent-os, the `@agentos-software/*` packages are pinned **per-package** in the workspace catalog. Manage the pins with the `just` recipes (never hand-edit them):\n\n```bash\njust agentos-pkgs-status # current mode + pinned versions\njust agentos-pkgs-set-version coreutils 0.3.1 # pin one package\njust agentos-pkgs-update # re-pin all from the `latest` dist-tag\njust agentos-pkgs-update dev # ... or from another tag\n```\n\n## Local development\n\nBoth sides consume local builds by default:\n\n- **secure-exec**: the registry packages are pnpm workspace members, so its tests and examples always resolve the in-repo builds — publish nothing while iterating.\n- **agent-os**: the **committed** dependency state is file-based — `link:`/`path` deps into the sibling `../secure-exec` checkout, with a committed `secure-exec.ref` sha that CI materializes the sibling at. Keep a sibling checkout, build its registry packages (`just registry-native` + `just registry-build` there), and everything resolves locally with no mode flipping. Advance the dependency with `just secure-exec-bump [sha]`.\n\nPublished-version pins exist only transiently inside agent-os publish workflows (`release-swap`): previews auto-cut a secure-exec preview at the committed ref, releases pin a real secure-exec release — and the swap is never committed.\n\n## Publishing from your own repo\n\nThe toolchain is not registry-specific — any repo can produce and publish agentOS packages with `npx @rivet-dev/agentos-toolchain`:\n\n```bash\n# a package dir with package.json + agentos-package.json + your compiled binaries\nnpx @rivet-dev/agentos-toolchain stage --commands-dir ./build/wasm\nnpx @rivet-dev/agentos-toolchain build\nnpx @rivet-dev/agentos-toolchain publish --tag dev # or --latest for a release\n```\n\nFor a JS agent, `pack` replaces `stage`/`build`:\n\n```bash\nnpx @rivet-dev/agentos-toolchain pack . --out dist/package --agent my-acp-entrypoint\n```\n\nThe published package is a plain npm dependency — consumers import its descriptor and pass it to `software` exactly like the registry packages. See [Software Definition](/docs/custom-software/definition) for the descriptor shape.","src/content/docs/docs/custom-software/publishing.mdx","1eb8d9ed6ad8e23f"] \ No newline at end of file +[["Map",1,2,9,10],"meta::meta",["Map",3,4,5,6,7,8],"astro-version","5.18.2","content-config-digest","b13dc6e1e58a194d","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"site\":\"https://agentos-sdk.dev\",\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4399,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":false,\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[null,null,null,null,null],\"rehypePlugins\":[null,[null,{\"strategy\":\"pre-mermaid\",\"mermaidConfig\":{\"theme\":\"dark\"}}],null,null,null,null],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[],\"actionBodySizeLimit\":1048576},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false}}","docs",["Map",11,12,20,21,28,29,36,37,44,45,52,53,60,61,68,69,76,77,84,85,92,93,100,101,108,109,9,116,122,123,130,131,138,139,146,147,154,155,162,163,170,171,178,179,186,187,194,195,202,203,210,211,218,219,226,227,234,235,242,243,250,251,258,259,266,267,274,275,282,283,290,291,298,299,307,308,315,316,323,324,331,332,339,340,347,348,355,356,362,363,370,371,378,379,386,387,394,395,402,403,410,411,418,419,426,427,434,435,442,443,466,467,482,483,501,502,517,518,539,540,563,564,580,581,599,600,616,617,634,635,651,652,667,668,683,684,699,700,717,718,734,735,750,751,767,768,784,785,800,801,817,818,834,835,850,851,867,868,883,884,899,900,915,916,934,935],"docs/agent-to-agent",{"id":11,"data":13,"body":17,"filePath":18,"digest":19,"deferredRender":16},{"title":14,"description":15,"skill":16},"Agent-to-Agent Communication","Use bindings to let agents communicate with each other.",true,"Agents communicate through [bindings](/docs/bindings). You define a bindings group that lets one agent send work to another, and the agent calls it like any other CLI command.\n\n## Example: code writer + reviewer\n\nThis example gives the writer agent a `review` binding. The writer sends the file's full contents (the VMs share no filesystem), and the binding writes them into a separate reviewer VM and sends a review prompt back through the reviewer.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/agent-to-agent/server.ts\" />\n\u003CCodeSnippet file=\"examples/agent-to-agent/client.ts\" />\n\u003C/CodeGroup>\n\nThe writer agent sees the review binding as a CLI command. Because the VMs share no filesystem, it sends the full file contents, not a path:\n\n```bash\nagentos-review submit --code \"$(cat api.ts)\"\n```\n\nThe binding writes the contents into the reviewer's VM, prompts the reviewer, and returns the review to the writer as JSON.\n\n## Why bindings?\n\nBindings are the natural communication layer between agents because:\n\n- **The agent doesn't need to know about other agents.** It just calls a binding. You can swap the implementation without changing the agent's behavior.\n- **No credentials in the VM.** The binding executes on the server, so it can access other agents directly without exposing connection details.\n- **Composable.** Chain any number of agents by adding more bindings. Each binding is a self-contained bridge to another agent.\n\n## Recommendations\n\n- Each agent has its own isolated VM and filesystem (they share no filesystem). Pass file contents through the binding input, then use `writeFile` in the binding to land them in the other VM.\n- Use [Workflows](/docs/workflows) to make multi-agent pipelines durable across restarts.","src/content/docs/docs/agent-to-agent.mdx","166030497fa3e2b1","docs/approvals",{"id":20,"data":22,"body":25,"filePath":26,"digest":27,"deferredRender":16},{"title":23,"description":24,"skill":16},"Approvals","Approve or deny agent tool use with human-in-the-loop or auto-approve patterns.","When an agent wants to use a tool (write a file, run a command, etc.), it asks for permission. You approve or deny that request, either interactively or with a server-side hook.\n\n- **Human-in-the-loop**: subscribe to `permissionRequest` on the client and respond per-request.\n- **Auto-approve**: use the `onPermissionRequest` server hook to decide without a client round-trip.\n- **Selective approval**: inspect the request and approve some, forward others to the client.\n\n## Permission request flow\n\nWhen an agent wants to use a tool, it emits a `permissionRequest`. Every request is delivered to two places at once, and you respond from whichever fits your app:\n\n- **On the client**: subscribe to the `permissionRequest` event and call `respondPermission(sessionId, permissionId, reply)`.\n- **On the server**: the `onPermissionRequest` hook on the actor runs for every request, with no client round-trip.\n- If neither responds, the request blocks until a reply arrives, then rejects after 120 seconds.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/approvals/client.ts\" />\n\n\u003CCodeSnippet file=\"examples/approvals/human-in-the-loop.ts\" />\n\u003C/CodeGroup>\n\nThe `permissionRequest` event payload:\n\n- **`data.sessionId`**: the session the request belongs to.\n- **`data.request.permissionId`**: the id to pass back to `respondPermission`.\n- **`data.request.description`**: human-readable summary of the requested action.\n- **`data.request.params`**: raw ACP permission details (requested tool, paths, etc.).\n\nReply options for `respondPermission`:\n\n| Reply | Behavior |\n|-------|----------|\n| `\"once\"` | Approve this single request |\n| `\"always\"` | Approve this and all future requests of the same type |\n| `\"reject\"` | Deny the request |\n\n## Patterns\n\n### Auto-approve\n\nThe `onPermissionRequest` hook runs server-side for every permission request before it reaches any client. Useful for fully automated pipelines.\n\n- **Signature**: `onPermissionRequest: async (sessionId, request) => { ... }`.\n- **Inspect**: `request.permissionId`, `request.description`, and `request.params`.\n- **Anything not handled** in the hook is forwarded to the client via the `permissionRequest` event.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/approvals/auto-approve.ts\" />\n\n\u003CCodeSnippet file=\"examples/approvals/auto-approve-client.ts\" />\n\u003C/CodeGroup>\n\n### Selective approval\n\nInspect the permission request to make approval decisions based on the tool or path. Approve some server-side, forward the rest to the client for human review.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/approvals/selective.ts\" />\n\n\u003CCodeSnippet file=\"examples/approvals/selective-client.ts\" />\n\u003C/CodeGroup>\n\n- For interactive applications, subscribe to `permissionRequest` on the client and build an approval UI.\n- If neither the server hook nor the client responds, the agent blocks until a response is given or the action times out.","src/content/docs/docs/approvals.mdx","534a7e2e87e53409","docs/architecture",{"id":28,"data":30,"body":33,"filePath":34,"digest":35,"deferredRender":16},{"title":31,"description":32,"skill":16},"Overview","A high-level tour of how agentOS works: the client / server / VM picture, the anatomy of a Linux VM (kernel + executor), agents and sessions, and the Rivet Actor orchestration underneath.","agentOS runs AI agents and untrusted code safely inside fully virtualized Linux VMs. Nothing the guest does touches your host directly: there is no real host filesystem, no real host network socket, and no real host process. Every guest operation is serviced by a kernel that agentOS owns.\n\nThis page is a high-level tour. It walks through the overall shape, the parts that make up a VM, how agent sessions work, and the orchestration layer underneath. Each section links out to a detailed page when you want to go deeper.\n\n## The big picture\n\nA running agentOS system has three roles: your **app** (the client), your **server** (which runs the sidecar that hosts the VMs), and the **VM** where guest code actually runs. Your app never runs guest code itself, it asks the server to.\n\n\u003Csvg viewBox=\"0 0 400 210\" role=\"img\" aria-label=\"A client (JavaScript, browser, or another backend) connects to an agentOS server. The server runs a sidecar that hosts many isolated VMs, each marked with the agentOS 'OS' logo; the sidecar brokers all guest syscalls and isolates each agent.\" style=\"width:100%;height:auto;max-width:420px;display:block;margin:2.5rem auto 0.5rem;\">\n \u003Cdefs>\n \u003Cmarker id=\"bp-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003Csymbol id=\"bp-os\" viewBox=\"0 0 100 100\">\n \u003Crect x=\"8\" y=\"8\" width=\"84\" height=\"84\" rx=\"26\" fill=\"none\" stroke=\"#1b1916\" stroke-width=\"8\" />\n \u003Ctext x=\"50\" y=\"50\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"var(--sl-font)\" font-weight=\"700\" font-size=\"38\" fill=\"#1b1916\">OS\u003C/text>\n \u003C/symbol>\n \u003C/defs>\n \u003Crect x=\"12\" y=\"67\" width=\"140\" height=\"60\" rx=\"12\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"82\" y=\"92\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"15\" font-weight=\"600\" fill=\"#1b1916\">Client\u003C/text>\n \u003Ctext x=\"82\" y=\"112\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#56524a\">JS · Browser · Backend\u003C/text>\n \u003Cline x1=\"154\" y1=\"97\" x2=\"205\" y2=\"97\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#bp-arrow)\" />\n \u003Crect x=\"210\" y=\"40\" width=\"164\" height=\"114\" rx=\"14\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"224\" y=\"62\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">Server\u003C/text>\n \u003Cg fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.2\">\n \u003Crect x=\"224\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"260\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"296\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"332\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"224\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"260\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"296\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"332\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003C/g>\n \u003Cg>\n \u003Cuse href=\"#bp-os\" x=\"229\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"265\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"301\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"337\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"229\" y=\"117\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"265\" y=\"117\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"301\" y=\"117\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#bp-os\" x=\"337\" y=\"117\" width=\"18\" height=\"18\" />\n \u003C/g>\n \u003Cg>\n \u003Crect x=\"150\" y=\"170\" width=\"15\" height=\"15\" rx=\"4\" fill=\"none\" stroke=\"#56524a\" stroke-width=\"1.4\" />\n \u003Ctext x=\"157.5\" y=\"178\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"var(--sl-font)\" font-weight=\"700\" font-size=\"7\" fill=\"#56524a\">OS\u003C/text>\n \u003Ctext x=\"174\" y=\"178\" dominant-baseline=\"central\" font-family=\"var(--sl-font)\" font-size=\"12\" fill=\"#56524a\">= an isolated VM\u003C/text>\n \u003C/g>\n\u003C/svg>\n\nThe client speaks to the agentOS server over the wire. The server runs the **sidecar**, the trusted core that hosts every VM: it owns each VM's kernel and brokers every guest syscall the agent makes (filesystem, processes, network, permissions) before carrying it out. Each VM is a fully isolated world, so agents are isolated from one another and from your host.\n\n### Your app (the client)\n\n- **Trusted caller.** Your app drives agentOS. It creates VMs, opens sessions, sends prompts, and reads results back.\n- **Never runs guest code.** The agent and any code it generates run in the VM, not in your app's process.\n- **Available everywhere.** There is a TypeScript client and a Rust client, and the same VM is reachable from a Node script, a browser/React app, or a separate backend.\n- **Owns the configuration.** Everything you send (VM setup, permission policy, resource limits, mounts) is trusted input. See the [Security Model](/docs/security-model) for why your configuration is not an attack surface.\n\n### Your server (the sidecar)\n\n- **The trusted core.** The sidecar is the part of the system that owns everything: the kernel, the virtual filesystem, the process and socket tables, pipes, PTYs, the permission policy, and DNS.\n- **The enforcement point.** Every request the VM makes is serviced here. The sidecar decides what is allowed before carrying it out.\n- **Hosts every VM.** A single sidecar manages many VMs side by side, each with its own kernel, filesystem, and process table, so every agent runs in its own isolated world. A crash or runaway in one VM never affects another.\n\n### The VM\n\n- **A fully virtualized Linux environment.** Each VM has its own filesystem, process table, and network policy. Two VMs share nothing.\n- **The unit of isolation.** Put one tenant or one task per VM to control the blast radius. A crash or runaway in one VM never affects another.\n- **Where guest code lives.** The agent, the shell, npm packages, and any generated code all run inside the VM, behind the kernel's boundary.\n\n## Anatomy of a Linux VM\n\nInside every VM there are two halves. The **kernel** is the trusted core that owns all the resources and rules. The **executor** is where untrusted guest code actually runs. Guest code can only *ask* the kernel for things, it never holds a real capability of its own.\n\n\u003Csvg viewBox=\"0 0 700 360\" role=\"img\" aria-label=\"A VM split into a kernel and an executor. The kernel owns the virtual filesystem, process table, socket table, pipes, PTYs, DNS, and permission policy. The executor runs guest JavaScript, WASM, and native binaries, and reaches the kernel through syscalls.\" style=\"width:100%;height:auto;max-width:680px;display:block;margin:1.5rem auto 0.5rem;\">\n \u003Cdefs>\n \u003Cmarker id=\"vm-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"12\" y=\"12\" width=\"676\" height=\"336\" rx=\"14\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"32\" y=\"40\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">The VM\u003C/text>\n\n \u003Crect x=\"32\" y=\"56\" width=\"636\" height=\"150\" rx=\"10\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.3\" />\n \u003Ctext x=\"52\" y=\"82\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">Kernel\u003C/text>\n \u003Ctext x=\"52\" y=\"100\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#56524a\">trusted core, every operation goes through here\u003C/text>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"11\" fill=\"#1b1916\">\n \u003Crect x=\"52\" y=\"116\" width=\"118\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"111\" y=\"135\" text-anchor=\"middle\">virtual filesystem\u003C/text>\n \u003Crect x=\"182\" y=\"116\" width=\"118\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"241\" y=\"135\" text-anchor=\"middle\">process table\u003C/text>\n \u003Crect x=\"312\" y=\"116\" width=\"118\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"371\" y=\"135\" text-anchor=\"middle\">socket table\u003C/text>\n \u003Crect x=\"442\" y=\"116\" width=\"92\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"488\" y=\"135\" text-anchor=\"middle\">pipes / PTYs\u003C/text>\n \u003Crect x=\"546\" y=\"116\" width=\"100\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"596\" y=\"135\" text-anchor=\"middle\">DNS\u003C/text>\n \u003Crect x=\"52\" y=\"156\" width=\"594\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"349\" y=\"175\" text-anchor=\"middle\">permission policy · network allowlist · resource limits\u003C/text>\n \u003C/g>\n\n \u003Cline x1=\"349\" y1=\"206\" x2=\"349\" y2=\"244\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#vm-arrow)\" />\n \u003Cline x1=\"319\" y1=\"244\" x2=\"319\" y2=\"206\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#vm-arrow)\" />\n \u003Ctext x=\"430\" y=\"228\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#56524a\">syscalls / replies\u003C/text>\n\n \u003Crect x=\"32\" y=\"248\" width=\"636\" height=\"84\" rx=\"10\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.3\" />\n \u003Ctext x=\"52\" y=\"274\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">Executor\u003C/text>\n \u003Ctext x=\"52\" y=\"292\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#56524a\">untrusted, runs guest code, holds no capabilities\u003C/text>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"11\" fill=\"#1b1916\">\n \u003Crect x=\"382\" y=\"262\" width=\"170\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"467\" y=\"281\" text-anchor=\"middle\">guest JavaScript (native V8)\u003C/text>\n \u003Crect x=\"562\" y=\"262\" width=\"84\" height=\"30\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"604\" y=\"281\" text-anchor=\"middle\">WASM\u003C/text>\n \u003Crect x=\"382\" y=\"298\" width=\"264\" height=\"22\" rx=\"6\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"514\" y=\"313\" text-anchor=\"middle\" font-size=\"10.5\">shell · coreutils · npm packages · native binaries\u003C/text>\n \u003C/g>\n\u003C/svg>\n\n### Kernel: the trusted core\n\n\u003Csvg viewBox=\"0 0 480 150\" role=\"img\" aria-label=\"Guest requests funnel into a single kernel chokepoint, which fans out to its owned subsystems: filesystem, processes, network, and policy.\" style=\"width:100%;height:auto;max-width:440px;display:block;margin:2.5rem auto;\">\n \u003Cdefs>\n \u003Cmarker id=\"kn-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"14\" y=\"58\" width=\"92\" height=\"34\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.3\" />\n \u003Ctext x=\"60\" y=\"79\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"11\" fill=\"#56524a\">guest request\u003C/text>\n \u003Cline x1=\"106\" y1=\"75\" x2=\"150\" y2=\"75\" stroke=\"#1b1916\" stroke-width=\"1.4\" marker-end=\"url(#kn-arrow)\" />\n \u003Crect x=\"154\" y=\"50\" width=\"92\" height=\"50\" rx=\"10\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.4\" />\n \u003Ctext x=\"200\" y=\"79\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"12\" font-weight=\"600\" fill=\"#1b1916\">Kernel\u003C/text>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#1b1916\">\n \u003Cline x1=\"246\" y1=\"60\" x2=\"286\" y2=\"22\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#kn-arrow)\" />\n \u003Cline x1=\"246\" y1=\"70\" x2=\"286\" y2=\"58\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#kn-arrow)\" />\n \u003Cline x1=\"246\" y1=\"80\" x2=\"286\" y2=\"92\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#kn-arrow)\" />\n \u003Cline x1=\"246\" y1=\"90\" x2=\"286\" y2=\"128\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#kn-arrow)\" />\n \u003Crect x=\"290\" y=\"8\" width=\"176\" height=\"26\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"378\" y=\"25\" text-anchor=\"middle\">filesystem\u003C/text>\n \u003Crect x=\"290\" y=\"46\" width=\"176\" height=\"26\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"378\" y=\"63\" text-anchor=\"middle\">processes\u003C/text>\n \u003Crect x=\"290\" y=\"80\" width=\"176\" height=\"26\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"378\" y=\"97\" text-anchor=\"middle\">network & DNS\u003C/text>\n \u003Crect x=\"290\" y=\"116\" width=\"176\" height=\"26\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"378\" y=\"133\" text-anchor=\"middle\">policy & limits\u003C/text>\n \u003C/g>\n\u003C/svg>\n\nThe kernel is the single chokepoint. Each kind of guest operation is serviced by a kernel-owned subsystem, never by a real host capability.\n\n- **Virtual filesystem.** A per-VM filesystem. Guest reads and writes hit the VFS, not your host disk.\n- **Process table.** A virtual process table. Child processes are kernel-managed and visible only inside their VM. No real host process is ever spawned for guest work.\n- **Socket table and DNS.** A virtual network stack. Outbound traffic is gated by the network allowlist.\n- **Pipes and PTYs.** Kernel-owned IPC and terminal devices, so shells and pipelines behave like real Linux.\n- **Policy and limits.** The kernel checks the applied permission policy, network allowlist, and resource limits on every request.\n\n### Executor: where guest code runs\n\n\u003Csvg viewBox=\"0 0 480 130\" role=\"img\" aria-label=\"The untrusted executor runs guest JavaScript, WASM, and native binaries. It holds no capabilities and reaches the kernel through syscalls.\" style=\"width:100%;height:auto;max-width:440px;display:block;margin:2.5rem auto;\">\n \u003Cdefs>\n \u003Cmarker id=\"ex-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"14\" y=\"14\" width=\"300\" height=\"102\" rx=\"10\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.4\" />\n \u003Ctext x=\"30\" y=\"36\" font-family=\"var(--sl-font)\" font-size=\"12\" font-weight=\"600\" fill=\"#1b1916\">Executor\u003C/text>\n \u003Ctext x=\"30\" y=\"52\" font-family=\"var(--sl-font)\" font-size=\"9.5\" fill=\"#56524a\">untrusted · no capabilities\u003C/text>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#1b1916\">\n \u003Crect x=\"30\" y=\"62\" width=\"80\" height=\"44\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"70\" y=\"88\" text-anchor=\"middle\">JS (V8)\u003C/text>\n \u003Crect x=\"120\" y=\"62\" width=\"80\" height=\"44\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"160\" y=\"88\" text-anchor=\"middle\">WASM\u003C/text>\n \u003Crect x=\"210\" y=\"62\" width=\"92\" height=\"44\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"256\" y=\"82\" text-anchor=\"middle\">native\u003C/text>\u003Ctext x=\"256\" y=\"96\" text-anchor=\"middle\">binaries\u003C/text>\n \u003C/g>\n \u003Cline x1=\"314\" y1=\"55\" x2=\"358\" y2=\"55\" stroke=\"#1b1916\" stroke-width=\"1.4\" marker-end=\"url(#ex-arrow)\" />\n \u003Ctext x=\"336\" y=\"48\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"9\" fill=\"#56524a\">syscall\u003C/text>\n \u003Cline x1=\"358\" y1=\"75\" x2=\"314\" y2=\"75\" stroke=\"#1b1916\" stroke-width=\"1.4\" marker-end=\"url(#ex-arrow)\" />\n \u003Ctext x=\"336\" y=\"92\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"9\" fill=\"#56524a\">reply\u003C/text>\n \u003Crect x=\"362\" y=\"38\" width=\"104\" height=\"54\" rx=\"10\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.4\" />\n \u003Ctext x=\"414\" y=\"70\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"12\" font-weight=\"600\" fill=\"#1b1916\">Kernel\u003C/text>\n\u003C/svg>\n\nThe executor is the untrusted half of the VM. It runs the guest code and reaches the kernel for everything else.\n\n- **JavaScript Acceleration.** Guest JavaScript runs on a native V8 runtime (the same engine in Chrome and Node.js, with the full JIT compiler) inside an isolate. This is what we call **JavaScript Acceleration**: the guest's JavaScript executes at native speed, not through an interpreter or a translation shim. It is genuinely fast, and it presents normal Node.js semantics. See [JavaScript Runtime](/docs/nodejs-runtime).\n- **WASM alongside it.** The shell (`sh`) and the coreutils behind process execution ship as WebAssembly modules, and you can run your own WASM too. See [POSIX Syscalls](/docs/architecture/posix-syscalls) and the [Compiler Toolchain](/docs/architecture/compiler-toolchain).\n- **Native binaries.** Tools mounted into the VM run inside the same boundary as everything else.\n- **No host fallthrough.** The executor holds no capability of its own. For every file read, process spawn, or socket open, it issues a syscall and blocks for the kernel's reply.\n\n### Processes & shell\n\n\u003Csvg viewBox=\"0 0 480 120\" role=\"img\" aria-label=\"exec, run, and spawn create entries in the kernel-owned virtual process table, with stdio bridged through pipes and PTYs.\" style=\"width:100%;height:auto;max-width:440px;display:block;margin:2.5rem auto;\">\n \u003Cdefs>\n \u003Cmarker id=\"pr-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"11\" fill=\"#1b1916\">\n \u003Crect x=\"14\" y=\"14\" width=\"92\" height=\"26\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"60\" y=\"31\" text-anchor=\"middle\">exec() / run()\u003C/text>\n \u003Crect x=\"14\" y=\"78\" width=\"92\" height=\"26\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"60\" y=\"95\" text-anchor=\"middle\">spawn / shell\u003C/text>\n \u003C/g>\n \u003Cline x1=\"106\" y1=\"27\" x2=\"172\" y2=\"52\" stroke=\"#1b1916\" stroke-width=\"1.3\" marker-end=\"url(#pr-arrow)\" />\n \u003Cline x1=\"106\" y1=\"91\" x2=\"172\" y2=\"66\" stroke=\"#1b1916\" stroke-width=\"1.3\" marker-end=\"url(#pr-arrow)\" />\n \u003Crect x=\"176\" y=\"36\" width=\"138\" height=\"46\" rx=\"10\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.4\" />\n \u003Ctext x=\"245\" y=\"56\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"11.5\" font-weight=\"600\" fill=\"#1b1916\">process table\u003C/text>\n \u003Ctext x=\"245\" y=\"71\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"9\" fill=\"#56524a\">virtual · per-VM\u003C/text>\n \u003Cline x1=\"314\" y1=\"59\" x2=\"360\" y2=\"59\" stroke=\"#1b1916\" stroke-width=\"1.3\" marker-end=\"url(#pr-arrow)\" />\n \u003Crect x=\"364\" y=\"40\" width=\"102\" height=\"40\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.2\" />\n \u003Ctext x=\"415\" y=\"64\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#1b1916\">pipes & PTYs\u003C/text>\n\u003C/svg>\n\n- **A real process model.** `exec()` and `run()` start fresh guest processes; you can also `spawn` long-running ones and open interactive shells.\n- **Kernel-managed.** Every process lives in the virtual process table, with stdio bridged through kernel-owned pipes and PTYs.\n- **Fresh each run.** Each `exec()` / `run()` starts a brand new guest process, so in-memory state never leaks from one run into the next.\n- See [Processes](/docs/architecture/processes) for the internals.\n\n### Virtual filesystem\n\n\u003Csvg viewBox=\"0 0 480 150\" role=\"img\" aria-label=\"The virtual filesystem layers a writable overlay over a snapshot root, plus mount points that graft host directories, S3, or cloud stores onto guest paths.\" style=\"width:100%;height:auto;max-width:440px;display:block;margin:2.5rem auto;\">\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"11\" fill=\"#1b1916\">\n \u003Crect x=\"60\" y=\"14\" width=\"360\" height=\"30\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.2\" />\u003Ctext x=\"240\" y=\"33\" text-anchor=\"middle\">overlay (guest writes)\u003C/text>\n \u003Crect x=\"60\" y=\"52\" width=\"360\" height=\"30\" rx=\"8\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.2\" />\u003Ctext x=\"240\" y=\"71\" text-anchor=\"middle\">root layer (snapshot)\u003C/text>\n \u003C/g>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"9.5\" fill=\"#1b1916\">\n \u003Crect x=\"60\" y=\"104\" width=\"108\" height=\"32\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"114\" y=\"124\" text-anchor=\"middle\">host dir mount\u003C/text>\n \u003Crect x=\"186\" y=\"104\" width=\"108\" height=\"32\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"240\" y=\"124\" text-anchor=\"middle\">S3 mount\u003C/text>\n \u003Crect x=\"312\" y=\"104\" width=\"108\" height=\"32\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"366\" y=\"124\" text-anchor=\"middle\">cloud store\u003C/text>\n \u003C/g>\n \u003Ctext x=\"240\" y=\"98\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"9\" fill=\"#56524a\">mount points grafted onto guest paths\u003C/text>\n\u003C/svg>\n\n- **Layered engines.** The VFS is a tree of engines: a root layer bootstrapped from a snapshot, an overlay for writes, and mount points that graft other backends onto guest paths.\n- **Host-backed mounts.** A guest path can be backed by a host directory, S3, or a cloud store. The kernel confines all guest I/O to the mount root, even against symlink and `..` tricks.\n- **Persisted.** The `/home/agentos` filesystem survives sleep/wake.\n- See [Filesystem](/docs/architecture/filesystem) for the internals.\n\n### Networking\n\n\u003Csvg viewBox=\"0 0 480 150\" role=\"img\" aria-label=\"Guest fetch, node:http, node:net, and WASM sockets all converge on one kernel socket table, which gates outbound traffic through the network allowlist.\" style=\"width:100%;height:auto;max-width:440px;display:block;margin:2.5rem auto;\">\n \u003Cdefs>\n \u003Cmarker id=\"nw-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#1b1916\">\n \u003Crect x=\"14\" y=\"10\" width=\"92\" height=\"24\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"60\" y=\"26\" text-anchor=\"middle\">fetch()\u003C/text>\n \u003Crect x=\"14\" y=\"42\" width=\"92\" height=\"24\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"60\" y=\"58\" text-anchor=\"middle\">node:http\u003C/text>\n \u003Crect x=\"14\" y=\"74\" width=\"92\" height=\"24\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"60\" y=\"90\" text-anchor=\"middle\">node:net\u003C/text>\n \u003Crect x=\"14\" y=\"106\" width=\"92\" height=\"24\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1\" />\u003Ctext x=\"60\" y=\"122\" text-anchor=\"middle\">WASM sockets\u003C/text>\n \u003C/g>\n \u003Cline x1=\"106\" y1=\"22\" x2=\"186\" y2=\"62\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#nw-arrow)\" />\n \u003Cline x1=\"106\" y1=\"54\" x2=\"186\" y2=\"66\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#nw-arrow)\" />\n \u003Cline x1=\"106\" y1=\"86\" x2=\"186\" y2=\"74\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#nw-arrow)\" />\n \u003Cline x1=\"106\" y1=\"118\" x2=\"186\" y2=\"78\" stroke=\"#1b1916\" stroke-width=\"1.2\" marker-end=\"url(#nw-arrow)\" />\n \u003Crect x=\"190\" y=\"48\" width=\"116\" height=\"44\" rx=\"10\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.4\" />\n \u003Ctext x=\"248\" y=\"68\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"11\" font-weight=\"600\" fill=\"#1b1916\">socket table\u003C/text>\n \u003Ctext x=\"248\" y=\"83\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"9\" fill=\"#56524a\">kernel-owned\u003C/text>\n \u003Cline x1=\"306\" y1=\"70\" x2=\"350\" y2=\"70\" stroke=\"#1b1916\" stroke-width=\"1.4\" marker-end=\"url(#nw-arrow)\" />\n \u003Crect x=\"354\" y=\"50\" width=\"112\" height=\"40\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.2\" />\n \u003Ctext x=\"410\" y=\"74\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#1b1916\">egress allowlist\u003C/text>\n\u003C/svg>\n\n- **One authoritative transport.** Guest `fetch()`, `node:http`, `node:net`, and WASM sockets all target the same kernel socket table. No part of guest networking opens a real host socket on its own.\n- **Egress policy.** Outbound traffic is gated by the network allowlist; loopback traffic stays confined to the VM.\n- **Preview URLs.** Servers a guest starts can be exposed through signed preview URLs.\n- See [Networking](/docs/architecture/networking) for the internals.\n\n\u003CNote>The security boundary that matters is between the trusted sidecar and the untrusted executor. Everything the guest tries to do crosses into the kernel, where the policy is checked before the operation runs. See the [Security Model](/docs/security-model) for the full threat model.\u003C/Note>\n\n## Agents & sessions\n\nAn agent (such as [Pi](https://github.com/mariozechner/pi-coding-agent)) is just another guest process running inside a VM, behind the same boundary as any other code. A **session** keeps that agent alive across many prompts and streams its output back to your app as events.\n\n\u003Csvg viewBox=\"0 0 700 210\" role=\"img\" aria-label=\"A client sends a prompt to an agent running inside a VM. The agent streams events back to the client and persists a transcript.\" style=\"width:100%;height:auto;max-width:680px;display:block;margin:1.5rem auto 0.5rem;\">\n \u003Cdefs>\n \u003Cmarker id=\"se-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"12\" y=\"66\" width=\"150\" height=\"78\" rx=\"12\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"87\" y=\"98\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"14\" font-weight=\"600\" fill=\"#1b1916\">Client\u003C/text>\n \u003Ctext x=\"87\" y=\"118\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#56524a\">your app\u003C/text>\n\n \u003Cline x1=\"162\" y1=\"92\" x2=\"266\" y2=\"92\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#se-arrow)\" />\n \u003Ctext x=\"214\" y=\"84\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#56524a\">prompt\u003C/text>\n \u003Cline x1=\"266\" y1=\"120\" x2=\"162\" y2=\"120\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#se-arrow)\" />\n \u003Ctext x=\"214\" y=\"136\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#56524a\">events\u003C/text>\n\n \u003Crect x=\"270\" y=\"30\" width=\"280\" height=\"150\" rx=\"12\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"290\" y=\"56\" font-family=\"var(--sl-font)\" font-size=\"12\" font-weight=\"600\" fill=\"#1b1916\">The VM\u003C/text>\n \u003Crect x=\"300\" y=\"72\" width=\"220\" height=\"56\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.2\" />\n \u003Ctext x=\"410\" y=\"98\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">Agent session\u003C/text>\n \u003Ctext x=\"410\" y=\"116\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#56524a\">long-lived agent process\u003C/text>\n\n \u003Cline x1=\"550\" y1=\"105\" x2=\"630\" y2=\"105\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#se-arrow)\" />\n \u003Crect x=\"560\" y=\"72\" width=\"118\" height=\"66\" rx=\"10\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.3\" />\n \u003Ctext x=\"619\" y=\"100\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"12\" font-weight=\"600\" fill=\"#1b1916\">Transcript\u003C/text>\n \u003Ctext x=\"619\" y=\"118\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#56524a\">persisted, replayable\u003C/text>\n\u003C/svg>\n\n### Sessions & transcripts\n\n- **Long-lived.** Where a bare `exec()` runs once and exits, a session keeps an agent alive across many prompts.\n- **Streamed.** The agent's output flows back to your app in real time as `sessionEvent`s.\n- **Replayable.** Each session persists a transcript (with sequence numbers) that survives sleep/wake, so you can replay the conversation later.\n- **Context injected.** agentOS adds a system prompt describing the VM environment and available commands and bindings, layered on top of the agent's own instructions. See [System Prompt](/docs/system-prompt).\n- See [Agent Sessions](/docs/architecture/agent-sessions) for the internals.\n\n### Permissions & approvals\n\n- **Two layers, different jobs.** The lower-level [permission policy](/docs/permissions) is enforced by the kernel on every guest syscall (nothing is allowed until you opt in). On top of that, [approvals](/docs/approvals) are about an agent asking before it uses a tool.\n- **Human-in-the-loop or automatic.** Subscribe to `permissionRequest` and respond per request, or use a server-side hook to decide without a client round-trip.\n- **Blocks until answered.** If neither your hook nor your client responds, the agent waits rather than proceeding.\n\n## Orchestration (Rivet Actors)\n\nThe `agentOS()` actor (from `@rivet-dev/agentos`) wraps the raw VM in a [Rivet Actor](/docs/core), which adds durable state, scheduling, and orchestration. This is what gives you persistence, cron, and workflows out of the box.\n\n\u003Csvg viewBox=\"0 0 700 200\" role=\"img\" aria-label=\"A Rivet Actor wraps an agentOS VM and adds durable state, cron scheduling, workflows, and sleep/wake persistence.\" style=\"width:100%;height:auto;max-width:680px;display:block;margin:1.5rem auto 0.5rem;\">\n \u003Crect x=\"40\" y=\"20\" width=\"620\" height=\"160\" rx=\"14\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"64\" y=\"46\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">Rivet Actor\u003C/text>\n \u003Ctext x=\"64\" y=\"64\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#56524a\">durable, addressable server object\u003C/text>\n\n \u003Crect x=\"64\" y=\"80\" width=\"180\" height=\"80\" rx=\"10\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.3\" />\n \u003Ctext x=\"154\" y=\"116\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">agentOS VM\u003C/text>\n \u003Ctext x=\"154\" y=\"136\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10\" fill=\"#56524a\">the virtual Linux VM\u003C/text>\n\n \u003Cg font-family=\"var(--sl-font)\" font-size=\"11.5\" fill=\"#1b1916\">\n \u003Crect x=\"272\" y=\"80\" width=\"120\" height=\"34\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.1\" />\u003Ctext x=\"332\" y=\"102\" text-anchor=\"middle\">Cron\u003C/text>\n \u003Crect x=\"412\" y=\"80\" width=\"120\" height=\"34\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.1\" />\u003Ctext x=\"472\" y=\"102\" text-anchor=\"middle\">Workflows\u003C/text>\n \u003Crect x=\"272\" y=\"126\" width=\"260\" height=\"34\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.1\" />\u003Ctext x=\"402\" y=\"148\" text-anchor=\"middle\">Persistence · sleep / wake\u003C/text>\n \u003Crect x=\"552\" y=\"80\" width=\"92\" height=\"80\" rx=\"7\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.1\" />\u003Ctext x=\"598\" y=\"116\" text-anchor=\"middle\">Durable\u003C/text>\u003Ctext x=\"598\" y=\"132\" text-anchor=\"middle\">state\u003C/text>\n \u003C/g>\n\u003C/svg>\n\n### What are actors?\n\n- **Durable server objects.** A Rivet Actor is a long-lived, addressable object with its own state. You reach a specific VM by name (`vm.getOrCreate(\"my-agent\")`).\n- **Stateful by default.** Unlike the bare core package, the actor keeps its filesystem and sessions persistent and handles distributed state for you.\n- **The portable runtime.** Actors give you a consistent way to run `agentOS()` on any infrastructure, with persistence, networking, and orchestration built in.\n\n### Cron\n\n- **Recurring work.** Schedule a shell command or an agent session on a cron expression.\n- **Overlap control.** Choose what happens when a run is still going when the next is due (`allow`, `skip`, or `queue`).\n- **Observable.** Stream `cronEvent`s to watch executions. See [Cron Jobs](/docs/cron).\n\n### Workflows\n\n- **Durable multi-step tasks.** A workflow is the actor's `run` handler wrapped in `workflow()`, where each `ctx.step()` is recorded, retried, and resumed independently.\n- **Crash-proof.** If the process dies mid-run, replay skips completed steps and continues where it left off.\n- **Composable.** The output of one step feeds the next: clone a repo, let an agent fix a bug, run the tests. See [Workflow Automation](/docs/workflows).\n\n### Persistence & sleep/wake\n\n- **Sleeps when idle.** After a grace period (15 minutes by default) with no activity, the VM sleeps to free resources.\n- **Wakes on demand.** It wakes automatically when a client connects or a cron job fires.\n- **What survives.** The `/home/agentos` filesystem, session records, transcripts, preview tokens, and cron definitions all persist. In-memory kernel state (running processes, open shells) does not. See [Persistence & Sleep](/docs/persistence).\n\n## Going deeper\n\nThis page is the map. Each subsystem has its own detailed page in the Advanced architecture section:\n\n- **[Agent Sessions](/docs/architecture/agent-sessions)**: how a session is bound to a VM, and how prompts and events flow end to end.\n- **[Processes](/docs/architecture/processes)**: the virtual process table, `exec()` / `run()`, child processes, and PTYs.\n- **[Filesystem](/docs/architecture/filesystem)**: the per-VM virtual filesystem, overlays, and host-backed mounts.\n- **[Networking](/docs/architecture/networking)**: the virtual socket table, DNS, the allowlist, and guest `fetch()`.\n- **[POSIX Syscalls](/docs/architecture/posix-syscalls)**: how WebAssembly guests behave like normal POSIX programs on top of the kernel.\n- **[Compiler Toolchain](/docs/architecture/compiler-toolchain)**: how the shell and coreutils are compiled to WebAssembly and mounted into the VM.\n- **[System Prompt](/docs/system-prompt)**: the context agentOS injects into every agent session.\n- **[Persistence & Sleep](/docs/persistence)**: what survives sleep/wake, and how VMs sleep and wake.\n\nFor the trust model and what counts as a sandbox escape, see the [Security Model](/docs/security-model).","src/content/docs/docs/architecture.mdx","e0003621fdf9ed78","docs/authentication",{"id":36,"data":38,"body":41,"filePath":42,"digest":43,"deferredRender":16},{"title":39,"description":40,"skill":16},"Authentication","Authenticate connections to agentOS actors using Rivet Actor connection params and hooks.","agentOS uses the same authentication system as [Rivet Actors](/docs/actors/authentication): clients send credentials as connection params, and you validate them server-side.\n\n- Clients pass credentials in `params` when they connect.\n- Validate them on the server in `onBeforeConnect` (throw to reject the connection), or extract user data into connection state with `createConnState` (read it in actions via `c.conn.state`).\n- You can declare the credential shape with `agentOS\u003CConnParams>(...)` to document what you accept, but the client's `params` is `unknown` and is not checked against it. The real check is your hook, not the types.\n- The current `@rivet-dev/agentos` runtime is an interim stub, so wiring these hooks end to end depends on the native runtime landing.\n\n## Example\n\nThe server declares the credential shape and validates it in `onBeforeConnect` (throw to reject); the client passes credentials as `params`.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/authentication/server.ts\" />\n\n\u003CCodeSnippet file=\"examples/authentication/client.ts\" />\n\u003C/CodeGroup>\n\nSee [Actor Authentication](/docs/actors/authentication) for JWT validation, role-based access control, external auth providers, and token caching.","src/content/docs/docs/authentication.mdx","49d6839b45690446","docs/benchmarks",{"id":44,"data":46,"body":49,"filePath":50,"digest":51,"deferredRender":16},{"title":47,"description":48,"skill":16},"Benchmarks","Performance benchmarks comparing agentOS to traditional sandbox providers.","These are the benchmark figures shown on the agentOS marketing page. All numbers are computed from the same data source used by the marketing page. For independent sandbox comparison data, see the [ComputeSDK benchmarks](https://www.computesdk.com/benchmarks/).\n\n## Cold start\n\nTime from requesting an execution to first code running. Measured using the sleep workload (a minimal VM running an idle Node.js process). Sandbox baseline: **E2B**, the fastest mainstream sandbox provider as of March 30, 2026. See [ComputeSDK benchmarks](https://www.computesdk.com/benchmarks/) for independent sandbox comparison data.\n\n| Metric | agentOS | Fastest sandbox (E2B) |\n|---|--:|--:|\n| Cold start p50 | 4.8 ms | 440 ms |\n| Cold start p95 | 5.6 ms | 950 ms |\n| Cold start p99 | 6.1 ms | 3,150 ms |\n\n## Memory per instance\n\nMeasured via staircase benchmarking:\n\n1. **Warmup.** A throwaway VM is created, started, and destroyed before measurement begins. This pays one-time costs (module cache, JIT compilation) that are amortized away in any real deployment where the host process is long-lived.\n2. **Baseline.** GC is forced twice (`--expose-gc`), then RSS is sampled across the entire process tree by reading `/proc/[pid]/statm` for the host process and all descendants. This captures child processes (e.g. V8 isolates running as separate processes) that `process.memoryUsage().rss` would miss.\n3. **Staircase.** VMs are added one at a time. After each VM starts and settles, GC is forced and RSS is sampled again. The delta from the previous sample is the incremental cost of that VM.\n4. **Average.** The per-VM cost is the mean of all step deltas.\n5. **Teardown.** All VMs are disposed and the reclaimed RSS is recorded.\n\nRSS is a process-wide metric that includes thread stacks and OS-mapped pages beyond the VM itself, so the reported figure is an upper bound on the true per-VM cost.\n\nSandbox baseline: **Daytona**, the cheapest mainstream sandbox provider as of March 30, 2026. Default sandbox: 1 vCPU + 1 GiB RAM.\n\n### Full coding agent\n\nPi coding agent session with MCP servers and mounted file systems.\n\n| Metric | agentOS | Cheapest sandbox (Daytona) |\n|---|--:|--:|\n| Memory per instance | ~131 MB | ~1024 MB |\n\n### Simple shell command\n\nMinimal shell workload running simple commands.\n\n| Metric | agentOS | Cheapest sandbox (Daytona) |\n|---|--:|--:|\n| Memory per instance | ~22 MB | ~1024 MB |\n\n## Cost per execution-second\n\nAssumes one agent per sandbox (needed for isolation) and 70% host utilization for self-hosted hardware (the industry-standard HPA scaling threshold). Cost formula: `server cost per second / concurrent executions per server`, where concurrent executions = `floor(server RAM / agent memory) × 0.7`.\n\nSandbox baseline: **Daytona** at $0.0504/vCPU-h + $0.0162/GiB-h with a 1 vCPU + 1 GiB minimum. Source: [daytona.io/pricing](https://www.daytona.io/pricing).\n\n### Full coding agent\n\n| Host tier | agentOS | Cheapest sandbox | Difference |\n|---|--:|--:|--:|\n| AWS ARM | $0.00000058/s | $0.000018/s | 32x cheaper |\n| AWS x86 | $0.00000072/s | $0.000018/s | 26x cheaper |\n| Hetzner ARM | $0.000000066/s | $0.000018/s | 281x cheaper |\n| Hetzner x86 | $0.00000011/s | $0.000018/s | 171x cheaper |\n\n### Simple shell command\n\n| Host tier | agentOS | Cheapest sandbox | Difference |\n|---|--:|--:|--:|\n| AWS ARM | $0.000000073/s | $0.000018/s | 254x cheaper |\n| AWS x86 | $0.000000090/s | $0.000018/s | 205x cheaper |\n| Hetzner ARM | $0.000000011/s | $0.000018/s | 1738x cheaper |\n| Hetzner x86 | $0.000000017/s | $0.000018/s | 1061x cheaper |\n\n## Test environment\n\n| Component | Details |\n|---|---|\n| CPU | 12th Gen Intel i7-12700KF, 12 cores / 20 threads @ 3.7 GHz, 25 MB cache |\n| RAM | 2× 32 GB DDR4 @ 2400 MT/s |\n| Node.js | v24.13.0 |\n| OS | Linux 6.1.0 (Debian), x86_64 |\n\n## Sandbox baselines\n\n| Comparison | Provider | Why this provider |\n|---|---|---|\n| Cold start | E2B | Fastest mainstream sandbox provider on [ComputeSDK](https://www.computesdk.com/benchmarks/) as of March 30, 2026 |\n| Memory and cost | Daytona | Cheapest mainstream sandbox provider as of March 30, 2026 ($0.0504/vCPU-h + $0.0162/GiB-h) |\n\nSelf-hosted hardware tiers: AWS t4g.micro (ARM, $0.0084/h, 1 GiB), AWS t3.micro (x86, $0.0104/h, 1 GiB), Hetzner CAX11 (ARM, €3.29/mo, 4 GiB), Hetzner CX22 (x86, €5.39/mo, 4 GiB). All on-demand pricing.\n\n## Reproducing\n\nagentOS benchmarks live in the [agent-os repository](https://github.com/rivet-dev/agentos) under `scripts/benchmarks/`.\n\n### Prerequisites\n\n- Node.js (see `.nvmrc`) and `pnpm`, with dependencies installed: `pnpm install`\n- A Rust toolchain (`cargo`) — the benchmarks build and run the native release sidecar\n- A reasonably **idle machine**: cold-start latency tails are sensitive to background CPU and GC jitter\n\n### Run everything\n\nFrom the repository root:\n\n```sh\n./scripts/benchmarks/run-benchmarks.sh\n```\n\nThe script builds the TypeScript packages and an **optimized (release) sidecar**, points the SDK at it via `AGENT_OS_SIDECAR_BIN`, and writes one JSON result per benchmark to `scripts/benchmarks/results/`:\n\n| Result file | Feeds marketing input |\n|---|---|\n| `coldstart-sleep.json` | `COLDSTART_P50/P95/P99_MS` |\n| `memory-sleep.json` | `MEMORY_SHELL_MB` (`result.avgPerVmRssBytes / 1024²`) |\n| `memory-pi-session.json` | `MEMORY_AGENT_MB` (`result.avgPerVmRssBytes / 1024²`) |\n\nCopy those numbers into `website/src/data/bench.ts`; every figure and multiplier on this page recomputes from them.\n\n### Run a single benchmark\n\nEach benchmark is a standalone `tsx` entrypoint. Build first (`pnpm build` and `cargo build --release -p agent-os-sidecar`), then:\n\n```sh\nexport AGENT_OS_SIDECAR_BIN=\"$PWD/target/release/agent-os-sidecar\"\n\n# Cold start (sleep workload)\npnpm exec tsx scripts/benchmarks/coldstart.bench.ts --workload=sleep --iterations=2000\n\n# Memory — simple shell command\npnpm exec tsx --expose-gc scripts/benchmarks/memory.bench.ts --workload=sleep --count=20\n\n# Memory — full coding agent\npnpm exec tsx --expose-gc scripts/benchmarks/memory.bench.ts --workload=pi-session --count=10\n```\n\nJSON goes to stdout; a human-readable table and progress go to stderr.\n\n### Sample sizes\n\nPercentiles are nearest-rank: `sorted[ceil(p/100 · n) − 1]`. With too few samples the tail is meaningless — at `n = 30`, **p99 is literally the single slowest run** and p95 is the second slowest. Use enough iterations that the reported percentile is averaged over real tail samples, not one outlier:\n\n| Statistic | Minimum iterations |\n|---|--:|\n| p50 (median) | ~30 |\n| p95 | ~200 |\n| p99 | ~1,000 |\n\n`run-benchmarks.sh` uses `--iterations=2000` for cold start so p95/p99 are trustworthy. Memory per VM is a mean of per-VM step deltas with low variance, so `--count=20` (shell) / `--count=10` (agent) is sufficient.\n\n> The `pi-session` memory workload needs a working in-VM agent runtime (the Pi adapter process must launch inside the VM). On builds where the agent runtime is unavailable it will fail its process check rather than report a number.\n\n### Methodology\n\nEvery benchmark **creates the sidecar once up front** (`AgentOs.createSidecar()`) and leases all VMs from it. VMs are **incremental tenants of one shared sidecar process** — not one process each — so the figures measure the marginal cost of a VM, not a fresh process. (`AgentOs.create()` with no `sidecar` option already uses the shared `default`-pool sidecar, so this is the default everywhere, including RivetKit actors.)\n\nBefore any measured iteration, each benchmark does a **cold run** — a throwaway VM that is created, started, and (for cold start) snapshotted. This pays the one-time process spawn + bootstrap so the recorded numbers reflect the warm, steady-state incremental per-VM cost, never the first VM. The release sidecar is required: a debug build is several times slower and inflates the numbers.","src/content/docs/docs/benchmarks.mdx","8b389f84726bb5c2","docs/bindings",{"id":52,"data":54,"body":57,"filePath":58,"digest":59,"deferredRender":16},{"title":55,"description":56,"skill":16},"Bindings","Expose custom host functions to agents as CLI commands inside the VM.","Expose your host JavaScript functions (defined with Zod input schemas) to agents as auto-generated CLI commands installed at `/usr/local/bin/agentos-{name}` inside the VM, injected into the agent's [system prompt](/docs/system-prompt) and callable inside scripts for code-mode token savings.\n\n## Getting started\n\nDefine a bindings group with Zod input schemas and pass it to `agentOS()`. Each binding becomes a CLI command inside the VM.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/bindings/server.ts\" />\n\n\u003CCodeSnippet file=\"examples/bindings/client.ts\" />\n\u003C/CodeGroup>\n\nEach binding can set an explicit `timeout` (in milliseconds) for long-running work. Bindings run without a timeout unless one is set.\n\n### Zod to CLI mapping\n\nZod schema fields are converted to CLI flags automatically. Field names are converted from camelCase to kebab-case.\n\n| Zod type | CLI syntax | Example |\n|---|---|---|\n| `z.string()` | `--name value` | `--path /tmp/out.png` |\n| `z.number()` | `--name 42` | `--limit 5` |\n| `z.boolean()` | `--flag` / `--no-flag` | `--full-page` |\n| `z.enum([\"a\",\"b\"])` | `--name a` | `--format json` |\n| `z.array(z.string())` | `--name a --name b` | `--tags foo --tags bar` |\n\nOptional fields (via `.optional()`) become optional flags. Required fields are enforced at validation time. Use `.describe()` on Zod fields to generate useful `--help` output.\n\n### What the agent sees\n\nWhen bindings are registered, CLI shims are installed at `/usr/local/bin/agentos-{name}` inside the VM and the binding list is injected into the agent's [system prompt](/docs/system-prompt), so keep binding descriptions concise to save tokens.\n\nThe agent interacts with bindings as shell commands:\n\nThe listing subcommand is still named `list-tools` for CLI compatibility.\n\n```bash\n# List all available bindings groups\nagentos list-tools\n\n# List bindings in a specific group\nagentos list-tools weather\n\n# Get help for a binding\nagentos-weather forecast --help\n\n# Call a binding with flags\nagentos-weather forecast --city Paris --days 3\n\n# Call a binding with inline JSON\nagentos-weather forecast --json '{\"city\":\"Paris\",\"days\":3}'\n\n# Call a binding with JSON from a file\nagentos-weather forecast --json-file /tmp/input.json\n```\n\nOn success, the binding exits `0` and writes a JSON envelope to stdout:\n\n```json\n{\"ok\":true,\"result\":{\"temperature\":22,\"condition\":\"sunny\"}}\n```\n\nOn failure (validation or execution error), the binding exits non-zero and writes the error message to stderr:\n\n```text\nMissing required flag: --city\n```\n\n## Bindings vs MCP servers\n\nagentOS supports two ways to give agents access to external functionality: **bindings** and **MCP servers**. Both work, but they have different tradeoffs.\n\n| | Bindings | MCP Servers |\n|---|---|---|\n| **How it works** | Call JavaScript functions on the host directly | Connect to a standard MCP server |\n| **Authentication** | None required. Direct binding to the agent's OS. | Requires custom auth configuration per server |\n| **Code mode** | Built-in. Bindings are exposed as CLI commands, so agents can call them inside scripts for up to 80% token reduction. | Requires extra work to make code mode work out of the box |\n| **Latency** | Near-zero. Bound directly to the host process. | Extra network hop to reach the MCP server |\n| **Setup** | Define bindings in your actor code with Zod schemas | Configure any standard MCP server |\n\nUse bindings when you want to expose your own JavaScript functions to agents. Use MCP servers when you want to connect to existing third-party services. See [Sessions](/docs/sessions#createsession-options) for MCP server configuration.\n\n## Security\n\nBinding calls from the agent securely invoke your `execute()` functions on the host. Your functions run with full access to the host environment, so you can call databases, APIs, and services directly without proxying credentials into the VM. The agent never sees the credentials, it only sees the binding's input/output contract.\n\nBindings run on the host with full access to the host environment, so do not expose bindings that could compromise the host without appropriate safeguards.","src/content/docs/docs/bindings.mdx","a88d54a3c6eadec8","docs/browser",{"id":60,"data":62,"body":65,"filePath":66,"digest":67,"deferredRender":16},{"title":63,"description":64,"skill":16},"Browser","Let agents read and search the web from an agentOS VM using Browserbase's cloud browser through the browse CLI — no local browser or sandbox required.","Agents can read and search the web with the [Browserbase](https://www.browserbase.com) `browse` CLI. The page loads in a real browser in Browserbase's cloud and comes back as clean content — the VM never runs a browser.\n\n## Setup\n\n\u003CSteps>\n\n1. **Create a Browserbase account**\n\n [Sign up](https://www.browserbase.com/sign-up) and grab your API key and project id from the [dashboard](https://www.browserbase.com/settings):\n\n ```bash\n export BROWSERBASE_API_KEY=bb_...\n export BROWSERBASE_PROJECT_ID=...\n ```\n\n2. **Install**\n\n ```bash\n npm install @rivet-dev/agentos @agentos-software/pi @agentos-software/browserbase\n ```\n\n3. **Add `browse` to the VM**\n\n \u003CCodeSnippet file=\"examples/browserbase/server-minimal.ts\" />\n\n \u003CAccordion title=\"Optional: install the browse skill\">\n Mount the [`browse` CLI skill](https://github.com/browserbase/stagehand/tree/main/packages/cli) into the agent's skills directory so it reaches for `browse` unprompted ([copy the skill folder from the example](https://github.com/rivet-dev/agentos/tree/main/examples/browserbase/skills)):\n\n \u003CTabs>\n \u003CTab title=\"Pi\">\n \u003CCodeSnippet file=\"examples/browserbase/server-skills-pi.ts\" />\n \u003C/Tab>\n \u003CTab title=\"Claude Code\">\n \u003CCodeSnippet file=\"examples/browserbase/server.ts\" />\n \u003C/Tab>\n \u003C/Tabs>\n \u003C/Accordion>\n\n4. **Use it**\n\n \u003CTabs>\n \u003CTab title=\"Agent uses browse\">\n \u003CCodeSnippet file=\"examples/browserbase/client-agent.ts\" />\n \u003C/Tab>\n \u003CTab title=\"Drive browse directly\">\n \u003CCodeSnippet file=\"examples/browserbase/client-direct.ts\" />\n \u003C/Tab>\n \u003C/Tabs>\n\n\u003C/Steps>\n\n## Command reference\n\n```bash\nbrowse cloud fetch https://example.com # retrieve a page as markdown\nbrowse cloud search \"web scraping tools\" # search the web\nbrowse cloud sessions list # list cloud browser sessions\nbrowse cloud projects list # list Browserbase projects\n```\n\n\u003CNote>\nThe [interactive driver mode](https://docs.browserbase.com/integrations/skills/browse-cli) (`browse open`, `browse click`, …) is not supported inside the VM yet ([#1631](https://github.com/rivet-dev/agentos/issues/1631)). For interactive automation, run `browse` inside a sandbox via [Sandbox Mounting](/docs/sandbox).\n\u003C/Note>","src/content/docs/docs/browser.mdx","068be90373494eaa","docs/core",{"id":68,"data":70,"body":73,"filePath":74,"digest":75,"deferredRender":16},{"title":71,"description":72,"skill":16},"Core Package","Use @rivet-dev/agentos-core standalone for direct VM control without the Rivet Actor runtime.","## agentOS vs agentOS Core\n\nThe `agentOS()` actor (from `@rivet-dev/agentos`) wraps the core package and adds:\n\n| | Core (`@rivet-dev/agentos-core`) | Actor (`@rivet-dev/agentos`) |\n|-|---|---|\n| Persistence | In-memory by default (pluggable via [mounts](#mounts)) | Persistent filesystem and sessions |\n| Distributed state | Manage yourself | Built-in distributed statefulness |\n| Stateful VMs | Complex to run yourself | Built into Rivet |\n| Sleep/wake | Manual `dispose()` / `create()` | Automatic |\n| Events | Direct callbacks | Broadcasted to all connected clients |\n| Preview URLs | None | Built-in signed URL server |\n| Multiplayer | N/A | Multiple clients on same actor |\n| Orchestration | N/A | Workflows, queues, cron |\n| Agent-to-agent communication | Custom | Built into [Rivet Actors](/docs/agent-to-agent) |\n| Authentication | Set up yourself | [Documentation](/docs/authentication) |\n\nWe recommend using [Rivet Actors](https://rivet.dev/docs/actors) because they provide a portable way to run `agentOS()` on any infrastructure with built-in persistence, networking, and orchestration. Use the core package if you need the most bare-bones implementation possible.\n\n## Install\n\n```bash\nnpm install @rivet-dev/agentos-core\n```\n\n## Boot a VM\n\nCreate a VM and drive it directly — no actor runtime, no client/server split. `AgentOs.create()` boots the VM in-process and returns a handle you call directly:\n\n\u003CCodeSnippet file=\"examples/core/vm.ts\" region=\"boot\" title=\"vm.ts\" />\n\n## Sidecar process\n\nEvery VM runs inside a **shared sidecar process** rather than a process of its own. By default all VMs are tenants of a single, process-global sidecar (the `default` pool), so each additional VM only adds its marginal cost — a V8 isolate plus its kernel state — instead of a whole OS process. This is what keeps per-VM memory in the tens of MB and warm VM creation in the single-digit milliseconds (see [Benchmarks](/docs/benchmarks)).\n\nThis is automatic — `agentOS()` and `AgentOs.create()` use the shared default sidecar with no configuration, and the same applies to Rivet Actors (each actor's VM is a tenant of the shared process). Disposing a VM tears down only that VM; the shared sidecar process is reused across VMs and stays alive for the lifetime of the host process.\n\nFor advanced cases the core package exposes explicit sidecar handles so you can isolate a group of VMs in their own process:\n\n\u003CCodeSnippet file=\"examples/core/advanced.ts\" title=\"advanced.ts\" />\n\n## Filesystem\n\n\u003CCodeSnippet file=\"examples/core/vm.ts\" region=\"filesystem\" title=\"vm.ts\" />\n\n## Processes\n\nLong-running process output is delivered through the `onStdout`/`onStderr` callbacks you pass to `spawn`, and exit through `onProcessExit(pid, …)`:\n\n\u003CCodeSnippet file=\"examples/core/vm.ts\" region=\"processes\" title=\"vm.ts\" />\n\n## Agent sessions\n\n`createSession` resolves to a session record; all session operations take its `sessionId`. Session events and permission requests are delivered through per-session callbacks (`onSessionEvent` / `onPermissionRequest`):\n\n\u003CCodeSnippet file=\"examples/core/vm.ts\" region=\"sessions\" title=\"vm.ts\" />\n\nRegister `onSessionEvent` right after `createSession` so you do not miss the live stream — core session events are live-only and are not replayed.\n\n## Networking\n\n`fetch(port, request)` reaches a server running inside the VM:\n\n\u003CCodeSnippet file=\"examples/core/vm.ts\" region=\"networking\" title=\"vm.ts\" />\n\n## Cron jobs\n\nCron jobs run an `\"exec\"` command or a `\"session\"` prompt on a schedule. Fired jobs are surfaced through the `onCronEvent` callback:\n\n\u003CCodeSnippet file=\"examples/core/vm.ts\" region=\"cron\" title=\"vm.ts\" />\n\n## Mounts\n\nConfigure filesystem backends at boot time.\n\nNative mount plugins (host directories, S3, etc.) are passed via `plugin`, each\nidentified by an `id` and a `config` object.\n\n\u003CCodeSnippet file=\"examples/core/mounts.ts\" title=\"mounts.ts\" />\n\n## Configuration reference\n\nAll VM configuration is passed to `AgentOs.create()` as a single flat object. This is the consolidated config block to copy and adapt. The [`agentOS()` actor](/docs/quickstart) accepts the same options and layers persistence, sleep/wake, and preview URLs on top:\n\n\u003CCodeSnippet file=\"examples/core/config-reference.ts\" title=\"config-reference.ts\" />\n\nThe top-level fields are documented inline above. See [Mounts](#mounts) and [Software](/docs/software).\n\n### Session events\n\nWith the core package, session events and permission requests are observed per-session on the `AgentOs` instance. `onSessionEvent(sessionId, event)` fires once for every session event; `onPermissionRequest(sessionId, request)` fires when an agent requests permission. Both are live-only callbacks — register them right after `createSession`:\n\n\u003CCodeSnippet file=\"examples/core/hooks.ts\" title=\"hooks.ts\" />\n\n### Timeouts and sleep\n\nAction timeouts and automatic sleep/wake are features of the [`agentOS()` actor](/docs/quickstart), not the core package. A core VM stays alive until you call `dispose()`. See [Persistence & Sleep](/docs/persistence) for the actor's sleep lifecycle.","src/content/docs/docs/core.mdx","9fdbf7fd824aad3d","docs/cost-evaluation",{"id":76,"data":78,"body":81,"filePath":82,"digest":83,"deferredRender":16},{"title":79,"description":80,"skill":16},"Cost Evaluation","How agentOS compares on cost to per-second sandbox providers when you run coding-agent VMs on your own hardware.","agentOS is a library you run on hardware you already control, not a metered service. That changes the cost model for running coding-agent VMs from \"pay a provider per sandbox-second\" to \"pay for the compute you provision, then pack as much work onto it as it can hold.\" This page explains where the savings come from and how to reason about them honestly. It does not publish a single magic multiplier, because the real number depends on your workload, your hardware, and how you share VMs.\n\n\u003CNote>For measured latency (cold start, warm execution, and reuse fast paths), see [Benchmarks](/docs/benchmarks). This page is about cost structure, not raw performance.\u003C/Note>\n\n## Where the savings come from\n\nTwo structural differences drive the cost gap versus per-second sandbox providers:\n\n- **You run on your own hardware**: you choose the cloud, instance type, architecture, and region. A small commodity instance (for example an ARM VM from a budget host) costs a flat hourly or monthly rate that is typically far below what per-sandbox-second billing adds up to once you have steady agent traffic. You also avoid egress fees and vendor lock-in.\n- **You decide the isolation granularity**: sandbox providers bill a full container or microVM per execution, usually with a minimum memory reservation that you pay for even when your code uses a fraction of it. With agentOS you own the VM lifecycle: you can dedicate a VM per tenant or per task for maximum isolation, or amortize setup by reusing one VM across many runs.\n\n## The isolation model matters for cost\n\nEach `AgentOs.create()` boots a fully virtualized VM, and each `exec()` / `run()` inside it is a fresh guest process. That gives you a dial between isolation and density:\n\n- **One VM per task or tenant (strongest isolation)**: create a VM, run the work, and dispose it, or give each tenant its own VM. Each VM is its own crash and resource domain, with the highest per-VM overhead. Best when load is untrusted or bursty.\n- **A shared VM for trusted work**: reuse one VM across many runs to amortize the VM boot cost. Each `exec()` / `run()` still executes in a fresh guest process, so in-memory state does not leak between runs, but the VM and filesystem are shared. Good for trusted, sequential work.\n\nThe denser you can safely pack agent work onto an instance, the lower your effective cost per execution. See [Resource Limits](/docs/resource-limits) for the per-VM caps that govern how densely you can pack, and [Processes & Shell](/docs/processes) for how guest processes run inside a VM.\n\n## How to estimate your own cost\n\nBecause agentOS runs on hardware you provision, the honest way to compare is to plug in your own numbers:\n\n1. **Pick your hardware and its rate**: take the hourly or monthly price of the instance you would run on, and divide down to a per-second instance cost.\n2. **Estimate how many concurrent VMs fit**: measure per-VM memory overhead on your target hardware under your isolation strategy, then divide your usable RAM by that figure. Leave headroom (the measurement and any orchestration layer will not bin-pack perfectly).\n3. **Divide instance cost by concurrent VMs**: that gives a cost-per-VM-second you can compare against a provider's per-sandbox-second rate.\n\n\u003CTip>Measure on the hardware and isolation strategy you will actually deploy. Per-VM overhead depends on whether you create a fresh VM per task or reuse one across runs, and on the work the agent does, so a number measured on one machine will not transfer cleanly to another.\u003C/Tip>\n\n## Comparing against sandbox providers\n\nWhen you do compare against a per-second sandbox provider, hold the methodology honest:\n\n- **Sandbox cost** is the provider's minimum allocatable memory times their per-GiB-second rate (plus any egress and platform fees). The minimum reservation is the floor you pay even for tiny workloads.\n- **agentOS cost** is your instance cost per second divided by the number of VMs you can keep live on it, with realistic headroom for bin-packing inefficiency.\n\nThe advantage is largest for **many small, short executions**, where a per-sandbox minimum reservation dominates and your own hardware lets you pack densely. It narrows for **heavyweight, long-lived workloads** (for example dev servers that need hundreds of megabytes regardless), where the win shifts from density to hardware choice: you still avoid per-second metering, egress fees, and lock-in, but the raw memory-density advantage is smaller.\n\n| Workload | Primary cost advantage |\n| --- | --- |\n| Many small, short executions | Density: pack many VMs per instance, no per-sandbox minimum |\n| Heavyweight, long-lived workloads | Hardware choice, flat instance pricing, no egress or lock-in |\n| High concurrency | Reuse a VM across runs to amortize VM boot cost |\n\n\u003CWarning>Be careful generalizing cost ratios from a single benchmark. Provider pricing, instance pricing, and exchange rates change over time, and per-VM overhead varies by workload and isolation strategy. Re-measure on your own hardware before quoting a number.\u003C/Warning>\n\nWhen you do need a full Linux sandbox for heavier agent workloads, see [agentOS vs Sandbox](/docs/versus-sandbox) for how the two models combine.","src/content/docs/docs/cost-evaluation.mdx","d061affd8af70768","docs/crash-course",{"id":84,"data":86,"body":89,"filePath":90,"digest":91,"deferredRender":16},{"title":87,"description":88,"skill":16},"Crash Course","Run coding agents inside isolated VMs with full filesystem, process, and network control.","\u003CNote>\nagentOS is in preview and the API is subject to change. If you run into issues, please [report them on GitHub](https://github.com/rivet-dev/rivet/issues) or [join our Discord](https://rivet.dev/discord).\n\u003C/Note>\n\n{/* SKILL_OVERVIEW_START */}\n\n## When to Use agentOS\n\n- **Coding agents**: Run any coding agent with full OS access, file editing, shell execution, and tool use.\n- **Automated pipelines**: CI-like workflows where agents clone repos, fix bugs, run tests, and open PRs.\n- **Multi-agent systems**: Coordinators dispatching to specialized agents, review pipelines, planning chains.\n- **Scheduled maintenance**: Cron-based agents that audit code, update dependencies, or generate reports.\n- **Collaborative workspaces**: Multiple users observing and interacting with the same agent session in realtime.\n\n## Minimal Project\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/minimal-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\nAfter the quickstart, customize your agent with the [Registry](/registry).\n\n## Agents\n\n### Sessions & Transcripts\n\nCreate agent sessions, send prompts, and stream responses in realtime. Transcripts are persisted automatically across sleep/wake cycles.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/sessions-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/sessions)*\n\n### Approvals\n\nApprove or deny agent tool use with human-in-the-loop patterns or auto-approve for trusted workloads.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/permissions-server.ts\" title=\"server.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/permissions-client.ts\" title=\"client.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/approvals)*\n\n### Bindings\n\nExpose your JavaScript functions to agents as CLI commands inside the VM. Each binding group becomes a binary at `/usr/local/bin/agentos-{name}`, and each binding becomes a subcommand with flags auto-generated from its Zod input schema. The server below defines a `weather` binding group with a `forecast` binding; the client opens a session and prompts the agent, which calls the binding itself as a shell command.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/bindings/server.ts\" title=\"server.ts\" />\n\n\u003CCodeSnippet file=\"examples/bindings/client.ts\" title=\"client.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/bindings) or [Documentation](/docs/bindings)*\n\n### Agent-to-Agent\n\nLet one agent call another through a [binding](/docs/bindings). The coder gets a `review` binding it invokes itself, which bridges into the reviewer's isolated VM.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/agent-to-agent-server.ts\" title=\"server.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/agent-to-agent-client.ts\" title=\"client.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/agent-to-agent)*\n\n### Multiplayer & Realtime\n\nConnect multiple clients to the same agent VM. All subscribers see session output, process logs, and shell data in realtime.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/multiplayer-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/multiplayer)*\n\n### Workflows\n\nOrchestrate multi-step agent tasks with durable workflows that survive crashes and restarts.\n\n\u003CCodeSnippet file=\"examples/crash-course/workflows.ts\" />\n\n[Documentation](/docs/workflows)\n\n## Operating System\n\n### Filesystem\n\nRead, write, and manage files inside the VM. The `/home/agentos` directory is persisted automatically across sleep/wake cycles.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/filesystem-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/filesystem)*\n\n### Processes & Shell\n\nExecute commands, spawn long-running processes, and open interactive shells.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/processes-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/processes)*\n\n### Networking & Previews\n\nProxy HTTP requests into VMs with `vmFetch`. Create preview URLs for port forwarding VM services to shareable public URLs.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/networking-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/networking)*\n\n### Cron Jobs\n\nSchedule recurring commands and agent sessions with cron expressions.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/crash-course/cron-client.ts\" title=\"client.ts\" />\n\n\u003CCodeSnippet file=\"examples/crash-course/server.ts\" title=\"server.ts\" />\n\u003C/CodeGroup>\n\n*See [Full Example](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course) or [Documentation](/docs/cron)*\n\n### Sandbox Mounting\n\nagentOS uses a hybrid model: agents run in a lightweight VM by default and mount a full sandbox on demand for heavy workloads like browsers, compilation, and desktop automation. Sandboxes are powered by [Sandbox Agent](https://sandboxagent.dev), so you can swap providers without changing agent code. Mount the sandbox as a filesystem and expose its process management as bindings.\n\n\u003CCodeSnippet file=\"examples/crash-course/sandbox.ts\" />\n\n[Documentation](/docs/sandbox)\n\n{/* SKILL_OVERVIEW_END */}","src/content/docs/docs/crash-course.mdx","c813a3d461363a4e","docs/debugging",{"id":92,"data":94,"body":97,"filePath":98,"digest":99,"deferredRender":16},{"title":95,"description":96},"Debugging","Capture agent logs and runtime (sidecar) logs to diagnose sessions, tool calls, and crashes.","Two log streams help diagnose what's happening inside a VM: the **agent's** own output and the **runtime (sidecar)** logs.\n\n## Agent logs (`onAgentStderr`)\n\nThe coding agent (ACP adapter) runs as a process inside the VM and uses **stdout for the ACP protocol**, so its **stderr** carries the agent's logs, warnings, and crash output — the first place to look when a tool call or session fails mid-turn. Capture it with `onAgentStderr` on the VM:\n\n\u003CCodeSnippet file=\"examples/debugging/agent-logs.ts\" />\n\nIt's a VM-level option covering every session's agent process; if omitted, chunks are written to the host `process.stderr` by default. See [Sessions → Agent logs](/docs/sessions#agent-logs).\n\n## Agent crashes (`onAgentExit`)\n\nIf the agent process exits without `closeSession()`, the runtime logs the exit, **auto-restarts the agent** (bounded to 3 restarts per session, re-attaching the same session id when the agent supports native resume), and fires `onAgentExit` with the outcome:\n\n```ts\nconst agentOs = await AgentOs.create({\n software: [pi],\n onAgentExit(event) {\n // event: { sessionId, agentType, processId, pid, exitCode,\n // restart: \"restarted\" | \"unsupported\" | \"failed\" | \"exhausted\",\n // restartCount, maxRestarts }\n console.warn(`agent exited (code ${event.exitCode}), restart=${event.restart}`);\n },\n});\n```\n\nOnly `restart === \"restarted\"` leaves the session usable; every other outcome means the session was evicted. The crash *reason* is on the agent's stderr (above); the exit event tells you it died and whether it recovered. See [Sessions → Agent crashes and auto-restart](/docs/sessions#agent-crashes-and-auto-restart).\n\n## Runtime logs (sidecar)\n\nThe agentOS sidecar emits structured **logfmt** logs for request handling, networking, and lifecycle. Configure them with environment variables on the **host process** (the sidecar inherits the host environment):\n\n| env var | effect |\n|---------|--------|\n| `AGENTOS_LOG_LEVEL` / `LOG_LEVEL` / `RUST_LOG` | log filter, in that priority. Uses [EnvFilter](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html) syntax, e.g. `debug`, `info`, `agentos_sidecar=debug,info`. Default `info`. |\n| `RUST_LOG_FORMAT` | `logfmt` (default) or `text` |\n| `AGENTOS_LOG_FILE` | append logs to this file instead of stderr (never stdout, which carries the wire protocol) |\n| `RUST_LOG_{SPAN_NAME,SPAN_PATH,TARGET,LOCATION,MODULE_PATH,ANSI_COLOR}` | per-field toggles (`=1` to enable) |\n\n```bash\nAGENTOS_LOG_LEVEL=debug AGENTOS_LOG_FILE=./sidecar.log RUST_LOG_FORMAT=logfmt node app.mjs\n```\n\nProduces logfmt lines such as:\n\n```text\nts=2026-… level=info message=\"ext request received\" kind=create_session\nts=2026-… level=info message=\"ext request handled\" kind=create_session elapsed_ms=1798\nts=2026-… level=debug message=\"querying: api.anthropic.com. A\"\n```\n\n\u003CNote>\nMost sidecar log activity is on the session/ACP path. A bare `AgentOs.create()` or a single `exec()` emits almost nothing — create a session (and send a prompt) to see request-handling logs.\n\u003C/Note>\n\nUse **agent logs** to see what the agent did (tool calls, model errors), and **runtime logs** to see what the sidecar did around it (request timing, DNS, lifecycle).","src/content/docs/docs/debugging.mdx","f006ad8031df8198","docs/deployment",{"id":100,"data":102,"body":105,"filePath":106,"digest":107,"deferredRender":16},{"title":103,"description":104,"skill":16},"Deploy","Choose the right deployment option for agentOS.","import DeployTargets from '../../../components/DeployTargets.astro';\n\nagentOS is powered by [Rivet](https://rivet.dev), an open-source actor platform, and runs as Rivet Actors. Three ways to run it in production:\n\n- **[Rivet Cloud](https://rivet.dev/cloud)**: fully managed (Rivet Compute, or bring your own cloud). Zero-ops.\n- **Self-hosted**: run the open-source Rivet platform on your own infrastructure (Kubernetes, Hetzner, VMs, and more) for full control.\n- **[agentOS Core](/docs/core)**: embed `@rivet-dev/agentos-core` directly in any Node.js backend, no platform required.\n\nPick a deploy target below, or see [Rivet's deployment guides](https://rivet.dev/docs/deploy/).\n\n## Deploy targets\n\nSee the [Rivet deploy docs](https://rivet.dev/docs/deploy/) for the full list. Available targets:\n\n\u003CDeployTargets />\n\n## Enterprise support\n\nEnterprise support and managed deployment are available, including dedicated support, custom SLAs, and compliance reviews. [Contact the Rivet team](https://rivet.dev/sales) to discuss your requirements.","src/content/docs/docs/deployment.mdx","2e9d172a6723589c","docs/filesystem",{"id":108,"data":110,"body":113,"filePath":114,"digest":115,"deferredRender":16},{"title":111,"description":112,"skill":16},"Filesystem","Read, write, mount, and manage files inside agentOS, all backed by a virtual filesystem isolated from the host disk.","Each VM has its own filesystem that the agent works in. Guest `fs` calls never touch the host disk, and it persists automatically across sleep/wake with no setup. See [Persistence](/docs/persistence) for the details.\n\n## Mounts\n\nBack a guest path with external storage by adding it to the `mounts` config. Each mount takes a `path` and an optional `readOnly` flag, and the guest only ever sees the mounted subtree, never the wider host.\n\n\u003CTabs>\n\n\u003CTab title=\"Host directory\">\n\nProject a real host directory into the filesystem, Docker-style. The guest sees only the mounted subtree, never the wider host filesystem. Path-escape attempts (symlinks, `..`, path aliasing) are confined to the mount root.\n\n\u003CCodeSnippet file=\"examples/filesystem/mount-host-dir.ts\" />\n\n\u003C/Tab>\n\n\u003CTab title=\"S3\">\n\nMount an S3 bucket with the built-in `s3` plugin. Pass an optional `prefix` to scope storage to a key path within the bucket, useful for sharing one bucket across multiple agents.\n\nThe backend is a block store, not a one-object-per-file mapping: file contents are split into fixed-size chunks (4 MB by default) stored as individual S3 objects, with a separate metadata layer mapping each file to its chunks. This keeps large files, partial reads and writes, and snapshots efficient without rewriting whole objects.\n\n\u003CCodeSnippet file=\"examples/filesystem/mount-s3.ts\" />\n\nThe `s3` plugin config also accepts `credentials` (`{ accessKeyId, secretAccessKey }`) and a custom `endpoint` for S3-compatible providers.\n\n\u003C/Tab>\n\n\u003CTab title=\"Google Drive\">\n\nMount a Google Drive folder with the built-in `google_drive` plugin.\n\n\u003CCodeSnippet file=\"examples/filesystem/mount-google-drive.ts\" />\n\n\u003C/Tab>\n\n\u003CTab title=\"In-memory\">\n\nUse the built-in `memory` plugin for an ephemeral mounted directory in the native RivetKit `agentOS()` actor.\n\n\u003CCodeSnippet file=\"examples/filesystem/server.ts\" />\n\n\u003C/Tab>\n\n\u003CTab title=\"Custom JS VFS\">\n\nUse `mountFs()` for a callback-backed JS filesystem driver. The driver must live in the same JS process as the `AgentOs` instance, such as direct core usage or a custom RivetKit actor that owns an `AgentOs` instance. `mountFs()` works on a running VM too — it returns a promise that resolves once the mount is visible to guest code, and rejects if delivery to the runtime fails.\n\n\u003CCodeSnippet file=\"examples/filesystem/mount-custom-vfs.ts\" />\n\nThe native `agentOS()` actor cannot accept `{ driver }` mounts in config because JS callback objects are not serializable across the native plugin boundary. Use `plugin` mounts there.\n\n\u003C/Tab>\n\n\u003C/Tabs>\n\n## File operations\n\nThese operations are primarily what the agent uses inside the VM, and are also available from the client to seed inputs and read results. For large or read-only inputs (a repo, a dataset), a read-only [host mount](#mounts) is faster than copying files in. Programs that need stdin or live output use exec instead (see [Core](/docs/core)).\n\n### Read and write\n\n\u003CCodeSnippet file=\"examples/filesystem/operations.ts\" region=\"read-write\" />\n\n### Batch read and write\n\n\u003CCodeSnippet file=\"examples/filesystem/operations.ts\" region=\"batch\" />\n\n### Directories\n\n\u003CCodeSnippet file=\"examples/filesystem/operations.ts\" region=\"directories\" />\n\n### File metadata\n\n\u003CCodeSnippet file=\"examples/filesystem/operations.ts\" region=\"metadata\" />\n\n### Move and delete\n\n\u003CCodeSnippet file=\"examples/filesystem/operations.ts\" region=\"move-delete\" />\n\n## Permissions\n\nFilesystem access is governed by the VM permission policy. The filesystem scope is granted by default; restrict it by path, for example to deny a sensitive directory:\n\n```ts\nconst vm = agentOS({\n permissions: {\n fs: {\n default: \"allow\",\n rules: [{ mode: \"deny\", operations: [\"*\"], paths: [\"/home/agentos/secrets/**\"] }],\n },\n },\n});\n```\n\nSee [Permissions](/docs/permissions) for the full configuration.\n\n## Sandboxes\n\nFor heavier or untrusted workloads, run a full Linux [sandbox](/docs/sandbox) alongside the VM and mount its filesystem into agentOS. The agent then reads and writes the sandbox's files through the same `fs` APIs while the sandbox handles execution. See [Sandbox Mounting](/docs/sandbox) for setup.\n\n## Default layout\n\nWith no `mounts` configured, every VM boots an Alpine-based root filesystem with the standard POSIX directories:\n\n- `/home/agentos`: the agent's home directory (`$HOME`) and default working directory (`pwd`) when spawned, where it reads and writes (mounts land under it, e.g. `/home/agentos/data`).\n- `/bin`, `/sbin`, `/usr`: installed commands (common POSIX utilities by default, plus any [software](/docs/software) you add).\n- `/etc`, `/lib`, `/opt`, `/root`, `/run`, `/srv`, `/tmp`, `/var`, `/mnt`: standard system paths.\n\nIt is backed by the VM's own filesystem and persisted across sleep/wake. Nothing comes from or touches the host disk.","src/content/docs/docs/filesystem.mdx","fd0d9d7a64dde670",{"id":9,"data":117,"body":119,"filePath":120,"digest":121,"deferredRender":16},{"title":118,"description":88},"Introduction","agentOS runs coding agents inside isolated VMs with full filesystem, process, and\nnetwork control — a lightweight VM in your own process with bindings, permissions,\nand orchestration built in.\n\n\u003CCardGroup>\n \u003CCard title=\"Quickstart\" href=\"/docs/quickstart\">\n Boot a VM and run your first coding agent.\n \u003C/Card>\n \u003CCard title=\"Crash Course\" href=\"/docs/crash-course\">\n Learn the core agentOS concepts.\n \u003C/Card>\n \u003CCard title=\"Agents\" href=\"/docs/agents/pi\">\n Run Pi, Claude Code, Codex, and OpenCode.\n \u003C/Card>\n\u003C/CardGroup>","src/content/docs/docs/index.mdx","df667a889276d10c","docs/js-runtime",{"id":122,"data":124,"body":127,"filePath":128,"digest":129,"deferredRender":16},{"title":125,"description":126,"skill":16},"JavaScript Runtime","How agentOS runs guest JavaScript: native V8 acceleration, low memory overhead, and Node.js compatibility.","The JavaScript runtime is powered by the Rivet [Secure Exec](https://secureexec.dev) project, which provides the isolated V8 runtime that agentOS runs guest code in. Every guest VM executes its JavaScript inside this runtime, fully sandboxed from the host.\n\n## JavaScript Acceleration\n\n- **JavaScript is unusually slow as WebAssembly**: unlike most software, JavaScript pays a heavy penalty when compiled to WebAssembly, because so much engineering has gone into JIT-compiling JavaScript directly in V8.\n- **Native V8, full JIT**: agentOS therefore runs guest JavaScript on the native V8 engine with its full JIT compiler, not through a WASM translation layer. We call this **JavaScript Acceleration**.\n- **Native-speed execution**: guest JavaScript runs at native speed while staying inside the isolation boundary, with normal Node.js semantics.\n\n## Comparison to Node.js efficiency\n\n- **Isolate model, not processes**: agentOS runs each agent inside a V8 isolate rather than spawning a full Node.js process per agent.\n- **Low memory overhead**: an isolate carries far less per-agent memory overhead than a full Node.js process, so many agents fit in the footprint that a process-per-agent model would spend on a handful.\n- **Benchmarks**: see the Secure Exec [benchmarks](https://secureexec.dev/docs/benchmarks) for cold start, warm execution, and reuse measurements.\n\n## Node.js compatibility\n\nGuest code runs as Node.js (reporting `process.version` as `v22.0.0`), but it never touches the host runtime. Every `node:` builtin resolves to a kernel-backed bridge or an in-isolate polyfill, never the real host module. For the full builtin matrix (`fs`, `net`, `http`, `crypto`, undici-backed `fetch`, and more), see the Secure Exec [Node.js Compatibility](https://secureexec.dev/docs/nodejs-compatibility) reference.\n\n### npm packages\n\nBy default the VM has no npm packages installed. Mount a host `node_modules` directory to give guest code access to real packages: the `nodeModulesMount` helper projects it read-only at `/root/node_modules`, and the in-kernel resolver walks it exactly like Node.js does, with no bundling or patching.\n\n\u003CCodeSnippet file=\"examples/js-runtime/node-modules-mount.ts\" />\n\nResolution matches naive Node.js over the mounted tree: the ancestor `node_modules` walk, `package.json` `exports`/`imports` and conditions, and `realpath`/symlink following (so pnpm and yarn layouts resolve too). Both ESM `import` and CommonJS `require` work. See the Secure Exec [module loading](https://secureexec.dev/docs/features/module-loading) guide for the full model.","src/content/docs/docs/js-runtime.mdx","bb45b5b55c2c8195","docs/limitations",{"id":130,"data":132,"body":135,"filePath":136,"digest":137,"deferredRender":16},{"title":133,"description":134,"skill":16},"Limitations","What the agentOS VM does not support, and how to work around it.","agentOS is a Linux environment with a POSIX-compliant virtual kernel. It handles most agent workloads (coding, scripting, file I/O, networking) with near-zero overhead.\n\n## Sandbox mounting\n\nWhen a workload needs a full Linux OS, agents can escalate to a full sandbox on demand without changing code. The [sandbox mounting](/docs/sandbox) extension mounts the sandbox as a filesystem and lets you execute commands on it, like mounting a hard drive on your own machine. Files written in the VM are available in the sandbox and vice versa.\n\nSee [agentOS vs Sandbox](/docs/versus-sandbox) for a detailed comparison.\n\n## Limitations\n\n### Software registry\n\nagentOS uses its own [software registry](/registry) of popular tools cross-compiled for the runtime. You cannot download and install arbitrary binaries (for example via `curl` or `apt`), and standard Linux package managers (`apt`, `yum`) are not available since agentOS runs a streamlined Linux environment rather than a full distribution. Native binaries that are not yet available in the registry (such as Go, Rust, or C++ toolchains) require a full [sandbox](/docs/sandbox).\n\nSee [Software](/docs/software) for how to install and configure available packages.\n\n### Lightweight Linux kernel\n\nagentOS provides a POSIX-compliant virtual Linux kernel with full filesystem operations, networking, and process management. It implements a focused subset of the kernel surface, so a few Linux-specific features are not available:\n\n- Kernel modules and eBPF\n- Container runtimes (e.g. Docker)\n- File watching (`inotify`, `fs.watch`)\n\n### No hardware access\n\nThe VM has no access to GPUs, USB devices, or other hardware.","src/content/docs/docs/limitations.mdx","9f65ad4a48fd07f0","docs/llm-credentials",{"id":138,"data":140,"body":143,"filePath":144,"digest":145,"deferredRender":16},{"title":141,"description":142,"skill":16},"LLM Credentials","Pass LLM API keys to agent sessions securely.","Pass LLM provider API keys to agent sessions so keys stay on the server and are injected at session creation, with per-tenant isolation for multi-tenant deployments.\n\n## Passing API keys\n\nPass LLM provider keys via the `env` option on `createSession`. The VM does not inherit from the host `process.env`, so keys must be passed explicitly.\n\n\u003CCodeSnippet file=\"examples/llm-credentials/client.ts\" />\n\n## Per-tenant credentials\n\nGive each tenant an isolated VM by keying `getOrCreate` on the tenant id, look up that tenant's API key on the server, and inject it via the session `env`. Credentials stay on the server and never reach the client.\n\nFirst, declare the agent software on the server:\n\n\u003CCodeSnippet file=\"examples/llm-credentials/server.ts\" />\n\nThen resolve each tenant's key and pass it at session creation:\n\n\u003CCodeSnippet file=\"examples/llm-credentials/per-tenant.ts\" />\n\nBecause keys are resolved per tenant from your own credential store (the `lookupTenantApiKey` stand-in above) and stay on the server, each session uses the tenant's own key and one tenant's key never reaches another tenant or the client.\n\n## Embedded LLM Gateway\n\nThe [Embedded LLM Gateway](/docs/llm-gateway) (coming soon) will remove the need to manage API keys manually. It routes all agent LLM requests through a managed proxy built into agentOS, providing per-tenant usage metering, rate limiting, and cost controls without deploying a separate gateway service.","src/content/docs/docs/llm-credentials.mdx","82c547d5f2ebc014","docs/llm-gateway",{"id":146,"data":148,"body":151,"filePath":152,"digest":153,"deferredRender":16},{"title":149,"description":150,"skill":16},"Embedded LLM Gateway","Route, meter, and manage LLM API calls from agents.","{/* TODO: This page is coming soon. */}\n\nThe Embedded LLM Gateway runs as part of the agentOS library, not as an external service. It intercepts and manages all LLM API calls made by agents inside the VM.\n\n- **Unified routing** for all agent LLM requests\n- **API keys stay on the server** so they are never exposed to agent code inside the VM\n- **Usage metering** with per-session and per-agent breakdowns\n- **Rate limiting** and cost controls\n\nCheck back soon for full documentation.","src/content/docs/docs/llm-gateway.mdx","cbe27275943ef64e","docs/multiplayer",{"id":154,"data":156,"body":159,"filePath":160,"digest":161,"deferredRender":16},{"title":157,"description":158,"skill":16},"Multiplayer","Connect multiple clients to the same agentOS actor for collaborative agent workflows.","Connect multiple clients to the same agentOS actor so all subscribers receive broadcasted session output, process logs, and shell data, enabling collaborative patterns where one user prompts and others observe.\n\n## Multiple clients observing a session\n\nAll clients connected to the same actor receive broadcasted events. This enables building collaborative UIs where multiple users watch an agent work.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/multiplayer/observe-session.ts\" />\n\n\u003CCodeSnippet file=\"examples/multiplayer/server.ts\" />\n\u003C/CodeGroup>","src/content/docs/docs/multiplayer.mdx","0f2695d49d1e4f9c","docs/cron",{"id":162,"data":164,"body":167,"filePath":168,"digest":169,"deferredRender":16},{"title":165,"description":166,"skill":16},"Cron Jobs","Schedule recurring commands and agent sessions in agentOS VMs.","Schedule recurring work with cron expressions, running either a shell command (`exec`) or an agent session (`session`), with overlap modes (`allow`, `skip`, `queue`) and `cronEvent` streaming to monitor execution. Cron jobs keep the actor alive while a job runs; the actor can sleep between executions.\n\n## Schedule a command\n\nRun a shell command on a recurring schedule. Pass a custom `id` to make a job easier to manage and cancel later.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/cron/schedule-command.ts\" />\n\u003CCodeSnippet file=\"examples/cron/server.ts\" />\n\u003C/CodeGroup>\n\n## Schedule an agent session\n\nCreate a recurring agent session that runs a prompt on a schedule.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/cron/schedule-session.ts\" />\n\u003CCodeSnippet file=\"examples/cron/server.ts\" />\n\u003C/CodeGroup>\n\n## Overlap modes\n\nControl what happens when a cron job triggers while a previous execution is still running.\n\n| Mode | Behavior |\n|------|----------|\n| `\"skip\"` | Skip this trigger if the previous run is still active |\n| `\"allow\"` | Allow concurrent executions (default) |\n| `\"queue\"` | Queue this trigger and run it after the previous one finishes |\n\nPrefer `\"skip\"` for most jobs to avoid unbounded concurrency if a run takes longer than the interval. Use `\"queue\"` when every trigger must eventually execute.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/cron/overlap.ts\" />\n\u003CCodeSnippet file=\"examples/cron/server.ts\" />\n\u003C/CodeGroup>\n\n## Monitor cron events\n\nSubscribe to the `cronEvent` event to track job execution. It is emitted whenever a cron job runs, carrying a single payload field:\n\n- **`data.event`**: A `CronEvent` describing the run.\n\n\u003CCodeSnippet file=\"examples/cron/monitor.ts\" region=\"subscribe\" />\n\nSubscribe before scheduling so you do not miss early runs.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/cron/monitor.ts\" />\n\u003CCodeSnippet file=\"examples/cron/server.ts\" />\n\u003C/CodeGroup>\n\n## List and cancel cron jobs\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/cron/list-cancel.ts\" />\n\u003CCodeSnippet file=\"examples/cron/server.ts\" />\n\u003C/CodeGroup>\n\n## Example: Heartbeat pattern\n\nSchedule a recurring agent session to periodically check on a task. This is the core pattern behind [OpenClaw](https://openclaw.org), where an agent wakes up on a schedule to review progress, take action, and go back to sleep.\n\n\u003CCodeSnippet file=\"examples/cron/heartbeat.ts\" region=\"heartbeat\" />\n\nThe agent sleeps between executions and only consumes resources when the cron job fires.","src/content/docs/docs/cron.mdx","91a73249b3cb5d29","docs/nodejs-runtime",{"id":170,"data":172,"body":175,"filePath":176,"digest":177,"deferredRender":16},{"title":173,"description":174,"skill":16},"Node.js Runtime","Run Node.js in agentOS: native V8 acceleration, the node CLI, installing packages, and Node.js compatibility.","agentOS runs **Node.js** (`process.version` `v22.0.0`), fully isolated from the host. `node`, `npm`, and `npx` are on the `PATH`.\n\n## JavaScript Acceleration\n\nNormally, JavaScript running inside WebAssembly is exceptionally slow. In agentOS, JavaScript runs inside a native V8 isolate (powered by [Secure Exec](https://secureexec.dev)) for native runtime speeds:\n\n- **Native V8 speed, no overhead** — guest JS runs on V8's full JIT, not a WASM translation layer.\n- **Lower memory than a Node.js process** — each agent is a V8 isolate, not a full process, so many fit where process-per-agent fits a handful. See [benchmarks](https://secureexec.dev/docs/benchmarks).\n\n## Running Node\n\n```ts\nawait agent.exec('node -e \"console.log(1 + 1)\"'); // inline\nawait agent.exec(\"node /workspace/main.js a b\"); // script + argv\nawait agent.exec(\"npx tsx script.ts\"); // npx\nawait agent.exec('echo \"console.log(42)\" | node'); // stdin\n```\n\n`node` works directly (`exec` / `execArgv` / `spawn`), through the guest shell (`sh -c`, pipes), and as a REPL.\n\n## Installing packages\n\n### Ahead of time\n\nMount a host `node_modules` tree — projected read-only at `/root/node_modules` and resolved exactly like Node.js (ancestor walk, `package.json` `exports`/`imports`, symlinks — so pnpm/yarn layouts work), for both `import` and `require`:\n\n```ts\nimport { agentOS, setup, nodeModulesMount } from \"@rivet-dev/agentos\";\n\nconst vm = agentOS({\n mounts: [nodeModulesMount(\"/absolute/path/to/node_modules\")],\n});\n```\n\n### At runtime\n\nOr install in the VM mid-task:\n\n```ts\nawait agent.exec(\"npm install chalk\");\nawait agent.exec(\"node /workspace/app.js\"); // app.js: require(\"chalk\")\n```\n\n## Node.js compatibility\n\nGuest code runs as Node.js v22, isolated from the host. `node:` builtins — `fs`, `net`, `http`, `crypto`, undici-backed `fetch`, and more — are provided by the runtime, never the host's. See the full [Node.js Compatibility](https://secureexec.dev/docs/nodejs-compatibility) matrix.","src/content/docs/docs/nodejs-runtime.mdx","de98ead7ed62a81c","docs/processes",{"id":178,"data":180,"body":183,"filePath":184,"digest":185,"deferredRender":16},{"title":181,"description":182,"skill":16},"Processes & Shell","Execute commands, spawn long-running processes, and open interactive shells in agentOS VMs.","Run commands with one-shot `exec`, spawn long-running processes with streaming stdout/stderr and stdin, manage their lifecycle (stop, kill, wait, inspect), open interactive PTY-backed shells, and inspect the process tree across all VM runtimes.\n\n## One-shot execution\n\nUse `exec` to run a command and wait for completion. Returns stdout, stderr, and exit code.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/processes/exec.ts\" />\n\u003CCodeSnippet file=\"examples/processes/server.ts\" />\n\u003C/CodeGroup>\n\n## Spawn a long-running process\n\nUse `spawn` for processes that run in the background. Output is streamed via `processOutput` and `processExit` events.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/processes/spawn.ts\" />\n\u003CCodeSnippet file=\"examples/processes/server.ts\" />\n\u003C/CodeGroup>\n\n## Write to stdin\n\nSend input to a running process.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/processes/stdin.ts\" />\n\u003CCodeSnippet file=\"examples/processes/server.ts\" />\n\u003C/CodeGroup>\n\n## Process lifecycle\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/processes/lifecycle.ts\" />\n\u003CCodeSnippet file=\"examples/processes/server.ts\" />\n\u003C/CodeGroup>\n\n## Interactive shells\n\nOpen an interactive shell with PTY support. Shell data is streamed via `shellData` events.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/processes/shell.ts\" />\n\u003CCodeSnippet file=\"examples/processes/server.ts\" />\n\u003C/CodeGroup>","src/content/docs/docs/processes.mdx","bea0f21a7222580c","docs/permissions",{"id":186,"data":188,"body":191,"filePath":192,"digest":193,"deferredRender":16},{"title":189,"description":190,"skill":16},"Permissions","The per-scope kernel permission policy that gates every guest syscall in the sandbox.","The sandbox permission policy is the kernel-level enforcement layer. Every guest syscall the agent's sandboxed code makes is checked against a per-scope policy before any host resource is touched.\n\n- **Six scopes**, configured independently: `fs`, `network`, `childProcess`, `process`, `env`, `binding`.\n- **Each scope** is a mode (`\"allow\"` or `\"deny\"`), or a rule set.\n- **A denied operation** is rejected with `EACCES` before any host resource is touched.\n- **Merged over a secure default**, so partial policies work.\n\nFor the higher-level agent tool-approval layer (human-in-the-loop, auto-approve), see [Approvals](/docs/approvals).\n\n## Defaults and merge semantics\n\nThe sandbox is deny-by-default for outward-facing capabilities. When you pass no policy, this baseline applies:\n\n```ts\n{\n fs: \"allow\", // virtualized in-memory filesystem only\n childProcess: \"allow\",\n process: \"allow\",\n env: \"allow\",\n network: \"deny\", // no network egress until you opt in\n}\n```\n\n- `fs`/`childProcess`/`process`/`env` are allowed because they are fully virtualized (the guest sees only the VM, never the host) and are required to run a program at all.\n- `network` is denied: guest code cannot reach the network until you opt in.\n- Your policy is merged **over** this baseline. Omitted scopes keep their default; they are **not** denied. So `{ network: \"allow\" }` grants the network while keeping the execution essentials.\n\n\u003CCodeSnippet file=\"examples/permissions/server.ts\" region=\"grant-network\" />\n\n## Permission scopes\n\n| Scope | Controls | Default |\n|---|---|---|\n| `fs` | Filesystem reads, writes, and metadata operations | `allow` |\n| `network` | Outbound connections: `fetch`, HTTP, DNS, and inbound `listen` | `deny` |\n| `childProcess` | Spawning child processes | `allow` |\n| `process` | Process-control operations | `allow` |\n| `env` | Environment variable access | `allow` |\n| `binding` | Invoking bindings registered with the runtime | `deny`* |\n\n\\* The `binding` scope is auto-granted to `allow` when you register bindings and set no `binding` policy of your own. Pass a `binding` policy to gate individual bindings.\n\n## Bind a policy to the VM\n\nA policy is a plain object keyed by scope. Pass it as `permissions` to `agentOS(...)` and it gates every guest syscall on that VM.\n\n\u003CCodeSnippet file=\"examples/permissions/bind-policy.ts\" />\n\n## Grant or deny a whole scope\n\nThe simplest value for a scope is a single mode string. `\"allow\"` permits every operation in the scope; `\"deny\"` rejects every one with `EACCES`. Omitted scopes keep their secure default, so you only list what you want to change.\n\n```ts\nconst permissions = {\n\tnetwork: \"allow\", // turn on network egress\n\tfs: \"deny\", // turn off all filesystem access\n};\n```\n\nThere is no typed `\"ask\"` mode. Interactive, human-in-the-loop approval lives in the higher-level [Approvals](/docs/approvals) layer, not the kernel policy. To block at the kernel level, use `\"deny\"`.\n\n## Allow only specific filesystem paths\n\nFor finer control, a scope can be a rule set instead of a bare mode: a `default` mode plus an ordered list of `rules`. The `fs` scope matches by `paths` (filesystem globs). Each rule names its `operations` (`read`, `write`, `stat`, `readdir`, `create_dir`, `rm`, `rename`, `symlink`, `readlink`, `chmod`, `truncate`, `mount_sensitive`, or `[\"*\"]` for all). Last matching rule wins; if no rule matches, `default` applies.\n\n\u003CCodeSnippet file=\"examples/permissions/fs-rules.ts\" region=\"deny-vault\" />\n\nTo invert it, flip `default` to `\"deny\"` and allow just one subtree:\n\n\u003CCodeSnippet file=\"examples/permissions/fs-rules.ts\" region=\"allow-only-data\" />\n\n## Allow only specific network hosts\n\nEvery non-`fs` scope matches by `patterns` instead of `paths`. For `network`, a pattern is a host (or `host:port`), and the operations are `fetch`, `http`, `dns`, and `listen`.\n\n\u003CCodeSnippet file=\"examples/permissions/server.ts\" region=\"allow-one-host\" />\n\n## Allow only specific bindings\n\nBindings registered with the runtime are gated by the `binding` scope, matched by name via `patterns`. Bindings have no sub-operations, so pass `[\"*\"]` for `operations`.\n\n\u003CCodeSnippet file=\"examples/permissions/server.ts\" region=\"allow-one-binding\" />\n\nThe `childProcess`, `process`, and `env` scopes work the same way: `childProcess` patterns match the command (`operations: [\"spawn\"]`), `env` patterns match the variable name (`operations: [\"read\", \"write\"]`), and `process` is matched by pattern with `operations: [\"*\"]`.\n\n## Combine policies and see denials\n\nEach policy above sets one scope, so you can spread several into one `permissions` object and bind them together.\n\n\u003CCodeSnippet file=\"examples/permissions/combine.ts\" />\n\nWhen a scope or matching rule denies an operation, the kernel rejects it with `EACCES` before any host resource is touched. For example, with `network: \"deny\"`, an outbound `fetch()` inside the guest throws:\n\n```\nEACCES: permission denied, tcp://example.com:80: blocked by network.http policy\n```","src/content/docs/docs/permissions.mdx","a5a4f270ee95e1ad","docs/persistence",{"id":194,"data":196,"body":199,"filePath":200,"digest":201,"deferredRender":16},{"title":197,"description":198,"skill":16},"Persistence & Sleep","How agentOS persists data and manages sleep/wake cycles.","agentOS automatically persists the `/home/agentos` filesystem and session transcripts (with sequence numbers for replay) across sleep/wake, sleeping after a configurable grace period (15 minutes by default) and waking automatically when a client connects or a cron job triggers.\n\n## What persists across sleep\n\n| Data | Storage | Persists? |\n|------|---------|-----------|\n| Files in `/home/agentos` | Persistent filesystem | Yes |\n| Session records | SQLite (`agent_os_sessions`) | Yes |\n| Session event history | SQLite (`agent_os_session_events`) | Yes |\n| Preview URL tokens | SQLite (`agent_os_preview_tokens`) | Yes |\n| Cron job definitions | Actor state | Yes |\n| Running processes | VM kernel | No |\n| Active shells | VM kernel | No |\n| In-memory mounts | VM memory | No |\n| VM kernel state | VM memory | No |\n\n## What prevents sleep\n\nThe actor stays awake as long as any of these are active:\n\n- **Active sessions** (created but not closed/destroyed)\n- **Running processes** (spawned but not exited)\n- **Active shells** (opened but not closed)\n- **Pending hooks** (server-side callbacks still executing)\n\nWhen all activity stops, the sleep grace period begins.\n\n## Sleep grace period\n\nAfter all activity stops, the actor waits 15 minutes before sleeping. This allows for brief pauses between interactions without restarting the VM.\n\n```\nActivity stops ──> 15 min grace period ──> Actor sleeps\n (VM shutdown, processes killed)\n\nNew client connects ──> Actor wakes ──> VM boots ──> Filesystem restored\n```\n\n## Timeouts\n\n| Setting | Default | Description |\n|---------|---------|-------------|\n| Action timeout | 15 minutes | Maximum time for any single action |\n| Sleep grace period | 15 minutes | Time before sleeping after all activity stops |\n\nThese are set internally by the `agentOS()` factory and cannot be overridden per-call.\n\n## Sleep vs destroy\n\n| | Sleep | Destroy |\n|-|-------|---------|\n| Filesystem | Preserved | Deleted |\n| Session records | Preserved | Deleted |\n| Event history | Preserved | Deleted |\n| Preview tokens | Preserved | Deleted |\n| VM state | Lost | Lost |\n| Processes | Killed | Killed |\n\n## VM boot and shutdown events\n\nSubscribe to `vmBooted` and `vmShutdown` events to track VM lifecycle.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/persistence/lifecycle-client.ts\" />\n\n\u003CCodeSnippet file=\"examples/persistence/server.ts\" />\n\u003C/CodeGroup>\n\n## Resuming after sleep\n\nWhen the actor wakes up, the VM boots and the filesystem is restored from SQLite, session records and event history are immediately available, and processes and shells from the previous session are gone. Clients can reconnect, list prior work with `listPersistedSessions` (which works without a running VM), and replay a session's persisted transcript with `getSessionEvents`.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/persistence/resume-client.ts\" />\n\u003C/CodeGroup>\n\n\n## Persisted tables schema\n\n### `agent_os_fs_entries`\n\nStores the virtual filesystem.\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `path` | TEXT PRIMARY KEY | File or directory path |\n| `is_directory` | INTEGER | 1 for directory, 0 for file |\n| `content` | BLOB | File content |\n| `mode` | INTEGER | POSIX mode bits |\n| `size` | INTEGER | File size in bytes |\n| `atime_ms` | INTEGER | Access time (ms) |\n| `mtime_ms` | INTEGER | Modification time (ms) |\n| `ctime_ms` | INTEGER | Change time (ms) |\n| `birthtime_ms` | INTEGER | Birth time (ms) |\n\n### `agent_os_sessions`\n\nStores session metadata.\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `session_id` | TEXT PRIMARY KEY | Unique session identifier |\n| `agent_type` | TEXT | Agent type (e.g. \"pi\") |\n| `capabilities` | TEXT (JSON) | Agent capabilities |\n| `agent_info` | TEXT (JSON) | Agent metadata |\n| `created_at` | INTEGER | Creation timestamp (ms) |\n\n### `agent_os_session_events`\n\nStores session event history.\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `id` | INTEGER PRIMARY KEY | Auto-incrementing ID |\n| `session_id` | TEXT | Session reference |\n| `seq` | INTEGER | Sequence number within session |\n| `event` | TEXT (JSON) | JSON-RPC notification |\n| `created_at` | INTEGER | Timestamp (ms) |","src/content/docs/docs/persistence.mdx","2ae78aaf60a662a8","docs/networking",{"id":202,"data":204,"body":207,"filePath":208,"digest":209,"deferredRender":16},{"title":205,"description":206,"skill":16},"Networking & Previews","Proxy HTTP requests into agentOS VMs and create shareable preview URLs.","Proxy HTTP requests into VM services with `vmFetch` and create time-limited, token-based preview URLs (with configurable expiration, revocation, and CORS), all carried over one transport (the kernel socket table) that is loopback-only by default under a three-layer confinement model.\n\n## Run an HTTP server in the VM\n\nGuest code runs a normal Node HTTP server: it binds a loopback port inside the VM exactly like any Node process. Write the server file and spawn it.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/networking/client-run-server.ts\" />\n\u003CCodeSnippet file=\"examples/networking/server.ts\" />\n\u003C/CodeGroup>\n\n## Fetch from a VM service\n\nWith the HTTP server running in the VM (above), send requests to it with `vmFetch`, including custom methods, headers, and body.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/networking/client-fetch.ts\" />\n\u003CCodeSnippet file=\"examples/networking/client-fetch-options.ts\" />\n\u003CCodeSnippet file=\"examples/networking/server.ts\" />\n\u003C/CodeGroup>\n\n## Preview URLs\n\nPreview URLs are port forwarding for VM services: a time-limited, public URL that proxies HTTP to a port inside the VM, for browser or external access (use `vmFetch` for server-to-server). Tokens survive sleep/wake and CORS is enabled; see [Security](/docs/security-model) for details.\n\n### Create a preview URL\n\nToken lifetimes are configured under the `preview` key:\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/networking/server-preview.ts\" />\n\u003CCodeSnippet file=\"examples/networking/client-preview.ts\" />\n\u003C/CodeGroup>\n\n### Revoke a preview URL\n\nMint short-lived preview tokens so access expires automatically; the lifetime is capped by `preview.maxExpiresInSeconds`.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/networking/client-revoke.ts\" />\n\u003CCodeSnippet file=\"examples/networking/server.ts\" />\n\u003C/CodeGroup>\n\n## Permissions\n\nNetwork access is governed by the VM permission policy. By default the guest cannot reach the network; grant it, or allow only specific destinations:\n\n```ts\nconst vm = agentOS({\n permissions: {\n network: {\n default: \"deny\",\n rules: [{ mode: \"allow\", operations: [\"*\"], patterns: [\"api.example.com\"] }],\n },\n },\n});\n```\n\nSee [Permissions](/docs/permissions) for the full configuration.","src/content/docs/docs/networking.mdx","be5a561ec89aa62e","docs/python-runtime",{"id":210,"data":212,"body":215,"filePath":216,"digest":217,"deferredRender":16},{"title":213,"description":214,"skill":16},"Python Runtime","Run Python in agentOS: the python CLI, installing packages ahead of time or at runtime, and what's supported.","agentOS runs **CPython 3.13** as a first-class runtime. `python` and `python3` are on the `PATH` and plug into the VM's filesystem, processes, and network — agents use them like any other command.\n\n## Running Python\n\n```ts\nawait agent.exec('python -c \"print(1 + 1)\"'); // inline\nawait agent.exec(\"python /workspace/main.py a b\"); // script + sys.argv\nawait agent.exec(\"python -m http.server 8000\"); // module\nawait agent.exec('echo \"print(40 + 2)\" | python -'); // stdin\n```\n\n`python` works directly (`exec` / `execArgv` / `spawn`), through the guest shell (`sh -c`, pipes), and as an interactive REPL.\n\n## Installing packages\n\n`pip install` writes to a persistent spot on the VM filesystem, so a package installed once is importable by every later `python` run in that VM.\n\n### Ahead of time\n\nInstall once during setup so the agent starts ready — no install cost mid-task:\n\n```ts\n// one-off setup pass on the VM, before handing it to the agent\nawait agent.exec(\"pip install requests pandas\");\n// requests + pandas now import in every python run on this VM\n```\n\n### At runtime\n\nOr let the agent install what it needs, mid-task:\n\n```ts\nawait agent.exec(\"pip install rich\");\nawait agent.exec('python -c \"import rich; print(rich.__version__)\"');\n```\n\n`pip install \u003Cpkg>` and `python -m pip install \u003Cpkg>` both work; downloads obey the VM's network policy (default-deny + allowlist).\n\n## Compatibility\n\nCPython 3.13 and the standard library, with a few VM-shaped differences.\n\n### Supported\n\n- Full VM filesystem (`/tmp`, `/etc`, `/root`, …) — shared with other processes and `readFile()`\n- Reading, writing, creating, deleting, and renaming files anywhere on the VM, plus symlinks and file metadata (`os.symlink` / `os.readlink` / `os.chmod` / `os.chown` / `os.utime`)\n- A real process in the tree: stdin/stdout/stderr, signals, `kill`\n- `subprocess` launching other VM commands (`node`, etc.)\n- Pure-Python packages, plus native packages with a prebuilt wheel — `numpy`, `pandas`, `scipy`, `scikit-learn`, `pydantic`, `cryptography`, `Pillow`, and [many more](https://pyodide.org/en/stable/usage/packages-in-pyodide.html)\n- `requests`, `urllib`, and `pip` over HTTP/DNS, under the VM network policy\n- Outbound raw TCP and UDP sockets (the `socket` module), under the VM network policy\n\n### Unsupported\n\n- OS threads and `multiprocessing` — the runtime is single-threaded\n- `os.fork` / `os.exec`\n- Some packages with native (C/Rust) extensions — see the [full list of supported packages](https://pyodide.org/en/stable/usage/packages-in-pyodide.html)\n- Socket servers / listeners (`bind`/`listen`/`accept`) — outbound connections only","src/content/docs/docs/python-runtime.mdx","46e34a931b33b0bc","docs/quickstart",{"id":218,"data":220,"body":223,"filePath":224,"digest":225,"deferredRender":16},{"title":221,"description":222,"skill":16},"Quickstart","Set up an agentOS actor, create a session, and run your first coding agent.","import DeployTargets from '../../../components/DeployTargets.astro';\nimport { AGENT_PROMPT } from '../../../components/marketing/agentPrompt';\n\n\u003Cdiv style=\"border-radius:0.75rem;border:1px solid rgba(27,25,22,0.12);background:rgba(27,25,22,0.035);padding:0.875rem 1.125rem;margin:1.5rem 0;color:#56524a;display:flex;align-items:center;justify-content:space-between;gap:1.25rem;\">\n\u003Cspan>Use this pre-built prompt to get started faster.\u003C/span>\n\u003Cbutton type=\"button\" onclick=\"var b=this;navigator.clipboard.writeText(b.getAttribute('data-prompt')||'').then(function(){b.textContent='Copied!';setTimeout(function(){b.textContent='Copy prompt';},1500);});\" data-prompt={AGENT_PROMPT} style=\"appearance:none;border:1px solid rgba(27,25,22,0.18);background:#1b1916;color:#f4f1e7;font-family:var(--sl-font);font-size:0.8rem;font-weight:600;display:inline-flex;align-items:center;justify-content:center;height:2rem;padding:0 0.85rem;border-radius:6px;cursor:pointer;white-space:nowrap;margin-top:0;flex:none;box-sizing:border-box;\">Copy prompt\u003C/button>\n\u003C/div>\n\n\u003Cdiv style=\"border-radius:0.75rem;border:1px solid rgba(27,25,22,0.12);background:rgba(27,25,22,0.035);padding:0.875rem 1.125rem;margin:1.5rem 0;color:#56524a;display:flex;align-items:center;justify-content:space-between;gap:1.25rem;\">\n\u003Cspan>Prefer to read code? Clone the example repository.\u003C/span>\n\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/quickstart-app\" style=\"appearance:none;border:1px solid rgba(27,25,22,0.18);background:transparent;color:#1b1916;font-family:var(--sl-font);font-size:0.8rem;font-weight:600;display:inline-flex;align-items:center;justify-content:center;height:2rem;padding:0 0.85rem;border-radius:6px;cursor:pointer;white-space:nowrap;text-decoration:none;flex:none;gap:0.45rem;box-sizing:border-box;\">\u003Csvg viewBox=\"0 0 496 512\" width=\"14\" height=\"14\" fill=\"currentColor\" aria-hidden=\"true\">\u003Cpath d=\"M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z\"/>\u003C/svg>View on GitHub\u003C/a>\n\u003C/div>\n\n\u003Csvg viewBox=\"0 0 400 210\" role=\"img\" aria-label=\"A client (JavaScript, browser, or another backend) connects to a server that runs each agent in its own isolated VM, marked with the agentOS 'OS' logo.\" style=\"width:100%;height:auto;max-width:420px;display:block;margin:3rem auto 2.5rem;\">\n \u003Cdefs>\n \u003Cmarker id=\"qs-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0,0 L10,5 L0,10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003Csymbol id=\"qs-os\" viewBox=\"0 0 100 100\">\n \u003Crect x=\"8\" y=\"8\" width=\"84\" height=\"84\" rx=\"26\" fill=\"none\" stroke=\"#1b1916\" stroke-width=\"8\" />\n \u003Ctext x=\"50\" y=\"50\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"var(--sl-font)\" font-weight=\"700\" font-size=\"38\" fill=\"#1b1916\">OS\u003C/text>\n \u003C/symbol>\n \u003C/defs>\n \u003Crect x=\"12\" y=\"67\" width=\"140\" height=\"60\" rx=\"12\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"82\" y=\"92\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"15\" font-weight=\"600\" fill=\"#1b1916\">Client\u003C/text>\n \u003Ctext x=\"82\" y=\"112\" text-anchor=\"middle\" font-family=\"var(--sl-font)\" font-size=\"10.5\" fill=\"#56524a\">JS · Browser · Backend\u003C/text>\n \u003Cline x1=\"154\" y1=\"97\" x2=\"205\" y2=\"97\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#qs-arrow)\" />\n \u003Crect x=\"210\" y=\"40\" width=\"164\" height=\"114\" rx=\"14\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"224\" y=\"62\" font-family=\"var(--sl-font)\" font-size=\"13\" font-weight=\"600\" fill=\"#1b1916\">Server\u003C/text>\n \u003Cg fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.2\">\n \u003Crect x=\"224\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"260\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"296\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"332\" y=\"76\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"224\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"260\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"296\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003Crect x=\"332\" y=\"112\" width=\"28\" height=\"28\" rx=\"5\" />\n \u003C/g>\n \u003Cg>\n \u003Cuse href=\"#qs-os\" x=\"229\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"265\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"301\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"337\" y=\"81\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"229\" y=\"117\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"265\" y=\"117\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"301\" y=\"117\" width=\"18\" height=\"18\" />\n \u003Cuse href=\"#qs-os\" x=\"337\" y=\"117\" width=\"18\" height=\"18\" />\n \u003C/g>\n \u003Cg>\n \u003Crect x=\"146\" y=\"170\" width=\"15\" height=\"15\" rx=\"4\" fill=\"none\" stroke=\"#56524a\" stroke-width=\"1.4\" />\n \u003Ctext x=\"153.5\" y=\"178\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"var(--sl-font)\" font-weight=\"700\" font-size=\"7\" fill=\"#56524a\">OS\u003C/text>\n \u003Ctext x=\"170\" y=\"178\" dominant-baseline=\"central\" font-family=\"var(--sl-font)\" font-size=\"12\" fill=\"#56524a\">= agentOS VM\u003C/text>\n \u003C/g>\n\u003C/svg>\n\n\u003CSteps>\n\n1. **Install**\n\n - **@rivet-dev/agentos** — Actor framework with built-in persistence and orchestration\n - **@agentos-software/pi** — [Pi](https://github.com/mariozechner/pi-coding-agent) coding agent. [Claude Code](/docs/agents/claude), [Codex](/docs/agents/codex), and [OpenCode](/docs/agents/opencode) install the same way.\n\n ```bash\n npm install @rivet-dev/agentos @agentos-software/pi\n ```\n\n2. **Create the server**\n\n \u003CCodeSnippet file=\"examples/quickstart-app/server.ts\" />\n\n3. **Create the client**\n\n The client can be any public frontend or another backend. The same `vm` actor is reachable from a plain Node script, a browser/React app, or a separate server.\n\n \u003CCodeGroup>\n \u003CCodeSnippet file=\"examples/quickstart-app/client.ts\" />\n\n \u003CCodeSnippet file=\"examples/quickstart-app/Agent.tsx\" />\n \u003C/CodeGroup>\n\n4. **Run it**\n\n Start the server, then run the client in a second terminal:\n\n ```bash\n # Terminal 1: start the server\n npx tsx server.ts\n\n # Terminal 2: run the client\n npx tsx client.ts\n ```\n\n5. **Customize**\n\n Now that you have a working agent, customize it to fit your needs:\n\n - **[Software](/docs/software)** — Install software packages inside the VM\n - **[Filesystem](/docs/filesystem)** — Read, write, and manage files inside the VM\n - **[Permissions & Resource Limits](/docs/permissions)** — Gate what the agent can do and cap its resource usage\n - **[Bindings](/docs/bindings)** — Expose your JavaScript functions to agents as CLI commands\n\n5. **Deploy**\n\n By default, agentOS runs locally with `npx rivetkit dev` — no infrastructure needed. To run in production, deploy to any of these targets:\n\n \u003CDeployTargets />\n\n See [Deployment](/docs/deployment) for managed, self-hosted, and agentOS Core options.\n\n\u003C/Steps>\n\n\u003CNote>\nagentOS is in preview and the API is subject to change. If you run into issues, please [report them on GitHub](https://github.com/rivet-dev/rivet/issues) or [join our Discord](https://rivet.dev/discord).\n\u003C/Note>\n\n## agentOS Core\n\nThe quickstart above uses `@rivet-dev/agentos`, which includes statefulness, multiplayer, and orchestration out of the box. If you only need direct VM control without those features, you can use the core package (`@rivet-dev/agentos-core`) standalone.\n\nSee [agentOS core documentation](/docs/core) for reference.","src/content/docs/docs/quickstart.mdx","e80fc774462c24df","docs/resource-limits",{"id":226,"data":228,"body":231,"filePath":232,"digest":233,"deferredRender":16},{"title":229,"description":230,"skill":16},"Resource Limits","Cap per-VM resources, JavaScript CPU/wall-clock time, Python execution, and WASM runtime work so guest code can never exhaust the host.","Every agentOS VM runs with **per-VM resource and runtime caps**. Runaway or malicious guest code can exhaust its own VM, but it can never starve the host or any sibling VM.\n\n- **Bounded by default**: each VM ships with conservative caps. Unset fields fall back to built-in defaults that match the runtime's historical constants.\n- **Per-VM**: every VM gets its own budget. Limits are not shared across VMs.\n- **Enforced by the sidecar/runtime**: a guest that exceeds a cap fails inside the VM (out-of-memory, `EMFILE`, `EAGAIN`, runtime timeout, etc.). The host is never affected.\n- **Operator-raisable**: the operator (the trusted process that creates the VM) may raise any cap for trusted workloads. Guest code can never raise its own caps.\n\n## Setting limits\n\nSet caps on the `limits` object in the `agentOS` config. Limits are grouped by subsystem (`resources`, `jsRuntime`, `python`, `wasm`, and more). Omitted limits keep their secure default.\n\n\u003CCodeSnippet file=\"examples/resource-limits/server.ts\" />\n\n## Available caps\n\n| Limit | Controls | Notes |\n|---|---|---|\n| `resources.maxProcesses` | Concurrent processes in the VM process table | Caps fork bombs and runaway spawning. New spawns fail with `EAGAIN`. |\n| `resources.maxOpenFds` | Open file descriptors | Exhausting the table fails with `EMFILE` / `ENFILE`. |\n| `resources.maxSockets` | Open sockets in the socket table | Bounds concurrent connections; excess `connect`/`accept` fail. |\n| `resources.maxFilesystemBytes` | Total bytes stored in the virtual filesystem | Bounds VFS storage; writes past the budget fail with a no-space error. |\n| `resources.maxWasmFuel` | WASM execution budget | Bounds WASM execution work; unset means no explicit fuel budget. |\n| `resources.maxWasmMemoryBytes` | WASM linear memory, in bytes | Default is `128 MiB`. |\n| `resources.maxWasmStackBytes` | Maximum WASM call-stack size, in bytes | Deep recursion fails with a stack overflow instead of crashing the VM. |\n| `jsRuntime.v8HeapLimitMb` | Guest JavaScript V8 heap, in MiB | Default is `128`. |\n| `jsRuntime.cpuTimeLimitMs` | Active JavaScript CPU time | Default is `30000`; `0` disables the CPU watchdog. |\n| `jsRuntime.wallClockLimitMs` | JavaScript elapsed wall-clock backstop | Default is `0`, disabled. Use this for finite commands, not long-lived adapters. |\n| `jsRuntime.importCacheMaterializeTimeoutMs` | Node import-cache materialization timeout | Default is `30000`. |\n| `jsRuntime.syncRpcWaitTimeoutMs` | JavaScript sync host-RPC wait | Unset keeps the engine default, currently `30000`. |\n| `python.executionTimeoutMs` | Python execution wall-clock timeout | Default is `300000`. |\n| `python.maxOldSpaceMb` | Pyodide runner V8 old-space heap, in MiB | Default is `0`, which keeps the engine default. |\n| `wasm.prewarmTimeoutMs` | WASM compile-cache warmup timeout | Default is `30000`. |\n| `wasm.runnerHeapLimitMb` | Trusted WASI/WASM runner V8 heap, in MiB | Default is `2048`; this is not guest linear memory. |\n\n## Behavior at the limit\n\n- **WASM stack**: deep recursion throws a stack-overflow error in the guest, never a host crash.\n- **JavaScript CPU time**: CPU-bound loops terminate with a CPU-budget error once active JS CPU exceeds `jsRuntime.cpuTimeLimitMs`.\n- **JavaScript wall time**: awaiting or blocked JS terminates only when you set `jsRuntime.wallClockLimitMs`; the default is disabled for long-lived adapters.\n- **Filesystem bytes**: writing past the VFS budget fails with a no-space error to the guest.\n- **Counts (fds / processes / sockets)**: hitting a table cap returns the standard POSIX errno (`EMFILE`, `EAGAIN`, etc.), exactly as a real Linux kernel would under `ulimit`.\n\n## Sidecar liveness\n\nSeparate from the guest caps above, the host detects a dead or wedged sidecar\nprocess by silence, not by per-request deadlines. The sidecar emits a liveness\nheartbeat every 10 seconds from a dedicated thread — so it keeps beating even\nmid-way through a long turn — and the host treats 30 seconds with no inbound\nframes at all as a dead sidecar: it kills the process and fails in-flight\nrequests with a typed `SidecarSilenceTimeout` error.\n\nBecause liveness is silence-based, individual requests have no time limit of\ntheir own: an agent turn may legitimately run for many minutes without being\ntorn down. Neither the heartbeat cadence nor the silence window is\nconfigurable — they are fixed protocol constants.\n\n## Warnings & observability\n\nLimits are observable, not just enforced. Every bound — resource caps and the\ninternal bounded queues alike — is tracked in a central limit registry that:\n\n- **Warns before the limit is hit.** As usage crosses ~80% of a cap, the runtime\n emits a structured warning (once per crossing, re-armed only after it recovers),\n so a slow consumer or a runaway guest is visible *before* it fails.\n- **Applies backpressure instead of failing catastrophically.** The internal\n queues between the guest, the runtime, and the host block their producer when\n full rather than dropping data or tearing down the session — so a transient\n burst degrades to \"slower\", not \"broken\".\n- **Surfaces through logs.** secure-exec logs to stderr (stdout is the wire\n protocol); set `AGENTOS_LOG=warn` (the default) to see near-limit warnings\n or `AGENTOS_LOG=debug` for live per-limit usage snapshots.\n\nSee [Limits & Observability](/docs/architecture/limits-and-observability) for the\nfull architecture.","src/content/docs/docs/resource-limits.mdx","59d5ba0df5c18f69","docs/sandbox",{"id":234,"data":236,"body":239,"filePath":240,"digest":241,"deferredRender":16},{"title":237,"description":238,"skill":16},"Sandbox Mounting","Extend agentOS with full sandboxes for heavy workloads like browsers, desktop automation, and compilation.","For heavy workloads like browsers, desktop automation, and compilation, pair agentOS with a full sandbox on demand. Its filesystem mounts into the VM as a native directory, and its process management is exposed as [bindings](/docs/bindings), all provider-agnostic through [Sandbox Agent](https://sandboxagent.dev).\n\n## Why use agentOS with a sandbox?\n\nagentOS is an alternative to sandboxes that covers most use cases, but some workloads need a full sandbox for special kinds of software (browsers, desktop automation, heavy compilation). Sandbox mounting lets you lazily start a sandbox on demand, only when it is needed, and project it into the VM. The hybrid model means one agent session can handle both lightweight coding tasks and heavy system operations, using the right tool for each.\n\nSee [agentOS vs Sandbox](/docs/versus-sandbox) for a detailed comparison.\n\n## When to use a sandbox\n\n- **Native binaries** not yet supported in the agentOS runtime.\n- **Browsers and desktop automation**: Playwright, Puppeteer, Selenium, or anything that needs a display server.\n- **Heavy compilation**: Large builds or native toolchains that require a full Linux environment.\n- **GUI applications**: Desktop apps, VNC sessions, or any workload that needs a graphical environment.\n- **Node.js packages with native extensions** (e.g. `sharp`, `bcrypt`, `better-sqlite3`) that require a full build toolchain.\n\nStart with the default agentOS VM for all workloads, and only spin up a sandbox when a task genuinely requires one. Sandboxes are billed per second of uptime, so start them on demand and tear them down when the task is done.\n\n## Getting started\n\nThe sandbox integration ships as the `@rivet-dev/agentos-sandbox` package. It works through two mechanisms:\n\n- **Filesystem mount**: Projects the sandbox into the VM as a native directory, like mounting a hard drive on your own machine. Read and write files through the mount directly.\n- **Bindings**: Exposes sandbox process management as [bindings](/docs/bindings). Execute commands on the sandbox from within the VM.\n\nBoth are powered by [Sandbox Agent](https://sandboxagent.dev), and you can swap providers without changing agent code. Install both packages:\n\n```bash\nnpm install @rivet-dev/agentos-sandbox sandbox-agent\n```\n\n`createSandboxFs` and `createSandboxBindings` come from `@rivet-dev/agentos-sandbox`. `SandboxAgent` and the provider helpers (such as `docker`) come from the `sandbox-agent` package.\n\nPass a provider as `sandbox: { provider: docker() }`. Agent OS starts a Sandbox Agent client, mounts it at `/mnt/sandbox`, registers the process bindings, and disposes the sandbox client when the VM is disposed. In RivetKit actors, pass the provider directly to `agentOS(...)`; the provider starts a fresh client for each actor VM.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sandbox/server.ts\" />\n\u003C/CodeGroup>\n\n## Calling the mounted bindings\n\nOnce the sandbox is mounted, write code through the filesystem and run it inside the sandbox. The sandbox bindings are exposed inside the VM as a CLI command, so you call it through the same `exec`/`spawn` surface as any other command.\n\n\u003CCodeSnippet file=\"examples/sandbox/client.ts\" />\n\n## Bindings reference\n\nThe bindings expose these commands inside the VM:\n\n```bash\n# Run a command synchronously\nagentos-sandbox run-command --command \"npm install\" --cwd \"/app\"\n\n# Start a background process\nagentos-sandbox create-process --command \"npm\" --args \"run\" --args \"dev\"\n\n# List running processes\nagentos-sandbox list-processes\n\n# Get process output\nagentos-sandbox get-process-logs --id \"proc_abc123\"\n\n# Stop or kill a process\nagentos-sandbox stop-process --id \"proc_abc123\"\nagentos-sandbox kill-process --id \"proc_abc123\"\n\n# Send input to an interactive process\nagentos-sandbox send-input --id \"proc_abc123\" --data \"yes\"\n```\n\n## Sandbox providers\n\nThe extension works with any [Sandbox Agent](https://sandboxagent.dev) provider. See the [Sandbox Agent documentation](https://sandboxagent.dev) for available providers and setup instructions.","src/content/docs/docs/sandbox.mdx","86ef3008884681d2","docs/security-model",{"id":242,"data":244,"body":247,"filePath":248,"digest":249,"deferredRender":16},{"title":245,"description":246,"skill":16},"Security Model","Trust boundaries, isolation guarantees, and the agentOS threat model.","\u003CWarning>\nagentOS is in beta and still undergoing security review. The security model described here is subject to change.\n\u003C/Warning>\n\nagentOS is a sandbox: it runs **untrusted code safely on behalf of a trusted caller**. Every actor boots its own fully virtualized VM with a virtual filesystem, process table, socket table, pipes, PTYs, a permission policy, and managed language runtimes. Guest JavaScript executes in a V8 isolate, and every guest syscall is serviced by the kernel rather than the host. There are no host escapes: guest code cannot spawn a real host process, touch the real host filesystem, or open a real host network socket.\n\n## Deny by default\n\nNo syscalls are bound to the system by default. Everything is denied until explicitly opted in.\n\n- **Network access** is denied until you opt in with a `network` permission.\n- **Filesystem mounts** expose nothing of the host until you configure them.\n- **Process spawning** runs only kernel-managed guest processes, never host processes.\n- **All other host capabilities** must be configured by the host before the VM can use them.\n\nOther in-VM scopes (the virtual filesystem, child processes, process info, env) are enabled so that normal programs run, but they are mediated entirely by the kernel and never touch the host.\n\n## Trust model: three components\n\nBefore judging whether something is a security bug, decide which side of the boundary it is on. agentOS has three components with very different trust levels.\n\n\u003Csvg width=\"700\" height=\"270\" viewBox=\"0 0 700 270\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"Three-component trust model: client, sidecar, executor\">\n \u003Crect x=\"20\" y=\"40\" width=\"180\" height=\"190\" rx=\"10\" fill=\"#f4f6f8\" stroke=\"#c8d0d8\" stroke-width=\"1.5\" />\n \u003Ctext x=\"110\" y=\"68\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#111827\">Client\u003C/text>\n \u003Ctext x=\"110\" y=\"90\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"12\" fill=\"#374151\">(trusted)\u003C/text>\n \u003Ctext x=\"110\" y=\"120\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">Your host app\u003C/text>\n \u003Ctext x=\"110\" y=\"138\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">Configures the VM\u003C/text>\n \u003Ctext x=\"110\" y=\"170\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#9a3412\">Untrusted: only the\u003C/text>\n \u003Ctext x=\"110\" y=\"186\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#9a3412\">code it submits\u003C/text>\n \u003Crect x=\"260\" y=\"40\" width=\"180\" height=\"190\" rx=\"10\" fill=\"#eef2f6\" stroke=\"#c8d0d8\" stroke-width=\"1.5\" />\n \u003Ctext x=\"350\" y=\"68\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#111827\">Sidecar / Kernel\u003C/text>\n \u003Ctext x=\"350\" y=\"90\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"12\" fill=\"#374151\">(trusted = TCB)\u003C/text>\n \u003Ctext x=\"350\" y=\"120\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">Owns VFS, processes,\u003C/text>\n \u003Ctext x=\"350\" y=\"138\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">sockets, policy\u003C/text>\n \u003Ctext x=\"350\" y=\"170\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">Enforces the\u003C/text>\n \u003Ctext x=\"350\" y=\"186\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">boundary\u003C/text>\n \u003Crect x=\"500\" y=\"40\" width=\"180\" height=\"190\" rx=\"10\" fill=\"#fdf2f2\" stroke=\"#e6b8b8\" stroke-width=\"1.5\" />\n \u003Ctext x=\"590\" y=\"68\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#111827\">Executor\u003C/text>\n \u003Ctext x=\"590\" y=\"90\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"12\" fill=\"#9a3412\">(untrusted = adversary)\u003C/text>\n \u003Ctext x=\"590\" y=\"120\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">V8 isolate / WASM\u003C/text>\n \u003Ctext x=\"590\" y=\"138\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#374151\">Runs guest code\u003C/text>\n \u003Ctext x=\"590\" y=\"170\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#9a3412\">Assume actively\u003C/text>\n \u003Ctext x=\"590\" y=\"186\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" fill=\"#9a3412\">hostile\u003C/text>\n \u003Cline x1=\"200\" y1=\"135\" x2=\"258\" y2=\"135\" stroke=\"#6b7280\" stroke-width=\"1.5\" marker-end=\"url(#arrow)\" />\n \u003Cline x1=\"440\" y1=\"135\" x2=\"498\" y2=\"135\" stroke=\"#b91c1c\" stroke-width=\"1.5\" stroke-dasharray=\"4 3\" marker-end=\"url(#arrowred)\" />\n \u003Ctext x=\"229\" y=\"128\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"9\" fill=\"#6b7280\">wire\u003C/text>\n \u003Ctext x=\"469\" y=\"128\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"9\" fill=\"#b91c1c\">syscalls\u003C/text>\n \u003Ctext x=\"469\" y=\"252\" text-anchor=\"middle\" font-family=\"Manrope, sans-serif\" font-size=\"11\" font-weight=\"700\" fill=\"#b91c1c\">SECURITY BOUNDARY\u003C/text>\n \u003Cdefs>\n \u003Cmarker id=\"arrow\" markerWidth=\"8\" markerHeight=\"8\" refX=\"6\" refY=\"3\" orient=\"auto\">\u003Cpath d=\"M0,0 L6,3 L0,6 Z\" fill=\"#6b7280\" />\u003C/marker>\n \u003Cmarker id=\"arrowred\" markerWidth=\"8\" markerHeight=\"8\" refX=\"6\" refY=\"3\" orient=\"auto\">\u003Cpath d=\"M0,0 L6,3 L0,6 Z\" fill=\"#b91c1c\" />\u003C/marker>\n \u003C/defs>\n\u003C/svg>\n\n### Client (trusted)\n\nThe party that configures and manages the VM: your application code, container, or serverless function.\n\n- The client process and **every value it sends** are trusted: VM config, mount descriptors and their plugin configs (host directory paths, S3 endpoints and credentials, etc.), the permission policy, network allowlist, resource limits, env, and DNS overrides.\n- **Configuration is not an attack surface.** A defect that requires the client to supply a malicious config, endpoint, credential, or policy is not a sandbox vulnerability: the client is configuring its own VM and already controls the host.\n- The **one** thing from the client that is *not* trusted is the **code/payload** it asks to run, because that runs in the executor. How code reached the executor never makes it trusted.\n\nYou are responsible for hardening this side. See [What you are responsible for](#what-you-are-responsible-for).\n\n### Sidecar / kernel (trusted, the enforcement point)\n\nThe trusted computing base. It brokers client requests and owns the kernel, VFS, mount/plugin registry, socket table, and permission policy. It is responsible for enforcing the boundary against the executor.\n\n### Executor (untrusted, the adversary)\n\nV8 isolates or WASM running guest JS/Python/WASM plus any third-party, npm, or agent-generated code.\n\n- Assume everything here is **actively hostile**.\n- The executor reaches the outside world only through kernel-owned VFS, process, socket, pipe, PTY, permission, and DNS paths.\n\n## The security boundary\n\n**The security boundary is sidecar ↔ executor.** The runtime must stop guest code in the executor from:\n\n- Escaping the kernel boundary (the real host filesystem, network, process table, or memory).\n- Bypassing the **applied** permission policy, allowlist, or limits.\n- Exhausting host resources beyond configured bounds.\n- Reading another VM's state.\n\nTwo corollaries that are easy to get wrong:\n\n- **Trusted policy, untrusted subject.** The permission policy and limits are trusted input, but the guest executor is the subject they bind. \"Guest bypasses an applied permission, egress rule, or resource cap\" is in-scope and serious. Trusted = who sets the rule; untrusted = who is bound by it.\n- **Trusted mount, untrusted traffic.** A host-backed mount (host directory, S3, etc.) comes from trusted config, so its existence, target, and credentials are not attack surface. But the guest drives I/O through it, so confining those guest operations to the mount root (symlink, `..`, TOCTOU, and path-aliasing escapes) is in-scope.\n\n### In scope vs out of scope\n\n| In scope (sandbox escape) | Out of scope (not a sandbox bug) |\n| --- | --- |\n| Guest reaches the real host fs / net / process / memory | Client supplies a malicious config / endpoint / credential / policy |\n| Guest bypasses an applied permission, egress rule, or limit | Hardening that only guards trusted client-provided configuration |\n| Guest exhausts host resources past configured bounds | Wire-level authn/authz between mutually distrusting clients |\n| Guest reads another VM's state | VM-to-VM access via forged connection IDs (single-client transport) |\n| Guest escapes a host-backed mount root (symlink / `..` / TOCTOU) | The existence or target of a configured mount |\n\n**Transport scope.** The wire protocol is same-version lockstep and single-client over stdio (one trusted client per sidecar process). There is no second, mutually-distrusting client, so wire-level authn/authz between clients and VM-to-VM access via forged connection IDs are out of scope until a multi-client transport exists.\n\n## VM isolation\n\nEach agentOS actor runs in its own isolated VM.\n\n- **Sandboxed execution.** All agent code runs inside a V8 isolate with WebAssembly. No code escapes the isolate boundary.\n- **Virtual filesystem.** The VM has its own in-memory filesystem. Guest reads and writes never reach the real host filesystem. Agents cannot access host files unless explicitly mounted.\n- **Virtual network.** The VM has no direct access to the host network. Outbound requests are proxied through the host with configurable controls.\n- **Process isolation.** No host process is visible or accessible from inside the VM.\n- **Per-actor containment.** Each actor is its own VM. Two actors share no filesystem, globals, module state, memory, or crash fate. The sidecar process that hosts those VMs may be shared by default as a performance optimization, but isolation is enforced at the VM level, not the host-process level.\n\n### Kernel-owned syscall paths\n\nEvery guest syscall is mediated by the kernel and checked against the runtime's permission policy. Concretely, the kernel mediates:\n\n- **Filesystem.** A virtual, in-memory filesystem. Guest reads and writes never reach the real host filesystem. Host data enters the VM only through the `files`, `mounts`, or `nodeModules` you configure explicitly. See [Filesystem](/docs/filesystem).\n- **Processes.** `node:child_process` spawns kernel-managed guest processes, never real host processes. Children can only run the commands the VM mounts (WASM-backed `sh` and coreutils, V8-backed `node`). See [Processes](/docs/processes).\n- **Network.** Guest `fetch()`, `node:http`, and raw sockets all flow through the kernel socket table. Guest `fetch()` runs through undici inside the isolate and then through the kernel socket table; it never opens a real host socket. See [Networking](/docs/networking).\n- **DNS, pipes, and PTYs** are likewise kernel-owned: no guest path reaches the host directly.\n- **Bindings.** Registered [bindings](/docs/bindings) are the only sanctioned way to hand the guest a named host capability. The guest invokes a binding by name with JSON input, the call round-trips to the host handler, and only the handler's return value comes back. The guest never receives the underlying host access.\n\n## What enters the VM\n\nThe host filesystem is never exposed to the guest by default. Host data crosses the boundary only through options you configure:\n\n- **`files`** seed bytes into the virtual filesystem. The bytes are copied in; the host path is never exposed.\n- **`mounts`** project a host directory at a guest path, Docker-style. The guest sees only the mounted subtree, read through the VFS lazily, never the wider host filesystem. Mounts are read-only unless you opt out.\n- **`nodeModules`** project a host `node_modules` directory (read-only, lazily) at a guest path so guest `import`/`require` resolves real installed packages.\n\nIn every case the guest sees only the subtree you mount, and writes to read-only mounts are rejected.\n\n## Permissions\n\nPermissions are the capability gate at the boundary. They merge over a secure default that denies the network and enables the filesystem, child processes, process info, and env. Because the merge is partial, you name only the scope you change.\n\n```ts\n// Grant network egress; everything else keeps the secure defaults.\npermissions: { network: \"allow\" }\n```\n\nA scope can be `\"allow\"`, `\"deny\"`, or a `{ default, rules }` policy that matches request patterns. Guest servers are reachable only over loopback inside the VM unless you exempt a port explicitly. See [Permissions](/docs/permissions) and [Networking](/docs/networking) for the full policy shape.\n\n## Resource and timing limits\n\nThe VM bounds guest execution so runaway or hostile code cannot hang or exhaust the host:\n\n- **Timeouts and cancellation** kill or cancel a run from the outside.\n- **Memory, CPU-time, and payload limits** are enforced by the VM.\n- **Timing-side-channel mitigation.** In the default mode, high-resolution clocks (`Date.now()`, `performance.now()`, `process.hrtime()`) are frozen within a run and `SharedArrayBuffer` is removed, to blunt timing side channels of the kind used in Spectre-style attacks.\n\nSee [Security & Auth](/docs/security-model) for resource limits, network control, and authentication setup.\n\n## What agentOS guarantees\n\n- Agent code cannot read or write host files outside configured mounts.\n- Agent code cannot make network requests except through the host proxy.\n- Agent code cannot access host environment variables or secrets.\n- Each actor's filesystem, sessions, and state are isolated from other actors.\n- Resource limits (CPU, memory) are enforced at the VM level.\n- A crash, resource exhaustion, or escape attempt is contained to a single VM; other VMs keep running, even when they share a sidecar process.\n\n## What you are responsible for\n\nThe boundary protects the host from the guest. It does **not** harden your host process against everything else. The VM alone is not enough without a hardened host, and a hardened host alone does not protect against code that runs with full host access inside your own process.\n\n- Hardening the host process and deployment environment. For internet-facing workloads that take untrusted input, run your host inside an already-hardened environment (for example AWS Lambda, Google Cloud Run, or a similar sandboxed platform).\n- Validating authentication tokens in `onBeforeConnect`.\n- Scoping [permissions](/docs/permissions) appropriately for your use case.\n- Managing API keys and secrets on the host side (use the [LLM gateway](/docs/llm-gateway) to avoid passing keys into the VM).\n- Configuring [resource limits and network controls](/docs/security-model) to match your threat model.\n- Choosing your blast radius: prefer a fresh VM per untrusted or high-risk task so an escape attempt cannot outlive a single VM.\n\n\u003CWarning>The boundary contains guest code, but you still own the host. Treat the host process as trusted infrastructure and harden it.\u003C/Warning>\n\n## Further reading\n\n- [Security configuration](/docs/security-model) for resource limits, network control, and authentication setup\n- [Permissions](/docs/permissions) for agent tool-use approval patterns\n- [agentOS vs Sandbox](/docs/versus-sandbox) for when to escalate to a full sandbox","src/content/docs/docs/security-model.mdx","e244aa6de3e29bdb","docs/sessions",{"id":250,"data":252,"body":255,"filePath":256,"digest":257,"deferredRender":16},{"title":253,"description":254,"skill":16},"Sessions","Create agent sessions, send prompts, stream responses, and subscribe to events.","Sessions launch an agent inside the VM, stream its responses in real time over `sessionEvent`, and persist a replayable ACP transcript that survives sleep/wake.\n\n## Create a session\n\nUse `createSession` to launch an agent inside the VM. Returns session metadata including capabilities and agent info. The agent starts in `/home/agentos` by default; override it with the `cwd` option below.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/create-session.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n### `createSession` options\n\nThe second argument to `createSession` accepts:\n\n- **`env`**: environment variables for the agent process (e.g. API keys). Not inherited from the host.\n- **`cwd`**: working directory inside the VM. Defaults to `/home/agentos`.\n- **`mcpServers`**: MCP servers (local child processes or remote URLs) exposing extra tools.\n- **`additionalInstructions`**: text appended to the agent's system prompt.\n- **`skipOsInstructions`**: skip the base OS instructions injection. Tool documentation is still included.\n\n## Send a prompt\n\nUse `sendPrompt` to send a message to an active session. The response contains the agent's reply.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/send-prompt.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Stream responses\n\nSubscribe to `sessionEvent` to receive real-time streaming output from the agent.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/stream-responses.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Cancel a prompt\n\nUse `cancelPrompt` to stop an in-progress prompt.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/cancel-prompt.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Close and destroy sessions\n\n- `closeSession` gracefully closes a session without removing persisted data\n- `destroySession` removes the session and all persisted data\n- To reconnect to a previously created session and replay its history, see [Replay events](#replay-events) and [Resuming a suspended session](/docs/architecture/agent-sessions#resuming-a-suspended-session)\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/close-destroy.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Runtime configuration\n\nChange model, mode, and thought level on a live session.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/runtime-config.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Replay events\n\nUse `getSessionEvents` to replay a session's persisted events, including for VMs that are not currently running. Pair it with `listPersistedSessions` to find earlier sessions.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/replay-events.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Persisted session history\n\nQuery session history from SQLite. Works even when the VM is not running.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/persisted-history.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Multiple sessions\n\nA single VM can run multiple sessions simultaneously. Each session has its own agent process but shares the same filesystem. Use different session IDs to manage them independently.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/sessions/multiple-sessions.ts\" />\n\u003CCodeSnippet file=\"examples/sessions/server.ts\" />\n\u003C/CodeGroup>\n\n## Agent logs\n\nThe agent (ACP adapter) runs as a process inside the VM. It uses **stdout** for ACP protocol traffic, so its **stderr** is the channel for logs, warnings, and crash diagnostics. Pass `onAgentStderr` to the VM to capture it, and route it to your own logger to see exactly what the agent is doing (or why it exited).\n\n\u003CNote>\n`onAgentStderr` is a VM-level option, so it covers every session's agent process. It's the fastest way to diagnose an agent that exits unexpectedly mid-turn; the crash reason surfaces here. If you omit it, chunks are written to the host `process.stderr` by default.\n\u003C/Note>\n\n\u003CCodeSnippet file=\"examples/sessions/server-logs.ts\" />","src/content/docs/docs/sessions.mdx","483d6169eed4df39","docs/software",{"id":258,"data":260,"body":263,"filePath":264,"digest":265,"deferredRender":16},{"title":261,"description":262,"skill":16},"Software","Install software packages and configure the commands available inside agentOS.","agentOS ships with a common set of POSIX utilities (coreutils, sed, grep, gawk, findutils, diffutils, tar, gzip) out of the box. The `software` option installs additional packages, each providing one or more CLI commands.\n\n## Install\n\n```bash\nnpm install @rivet-dev/agentos @agentos-software/pi\n```\n\nAdd packages like `@agentos-software/ripgrep` or `@agentos-software/jq` for anything beyond the default utilities. Browse the full catalog on the [Registry](/registry).\n\n## Usage\n\nImport the software packages you want, list them in the `software` array on the actor, then run commands through the client handle.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/software/server.ts\" />\n\n\u003CCodeSnippet file=\"examples/software/client.ts\" />\n\u003C/CodeGroup>\n\n## Available Packages\n\nBrowse all available software packages on the [Registry](/registry).\n\n## Custom Software\n\nPackage your own agents, command packages, and WASM commands. See [Software Definition](/docs/custom-software/definition) to define a package, and [Building Binaries](/docs/custom-software/building-wasm) to compile WASM commands from source in the [secure-exec registry](https://github.com/rivet-dev/secure-exec/tree/main/registry).","src/content/docs/docs/software.mdx","eae19d4d749a07f5","docs/system-prompt",{"id":266,"data":268,"body":271,"filePath":272,"digest":273,"deferredRender":16},{"title":269,"description":270,"skill":16},"System Prompt","How agentOS injects context into agent sessions.","agentOS automatically injects a system prompt into every agent session that describes the VM environment and available commands and bindings. The prompt is additive and never replaces the agent's own instructions (CLAUDE.md, AGENTS.md, etc.).\n\nThe base prompt is embedded in the sidecar (not written to a file inside the VM). At session start the sidecar assembles the base prompt with your additional instructions and generated binding docs, then injects the result into the agent adapter's launch arguments (for example, `--append-system-prompt` for Pi).\n\n## Customization\n\n- `additionalInstructions` appends extra instructions to the agent's system prompt. They are added after the base OS prompt and the generated binding docs, so they layer on top of (rather than replace) the agent's own instructions.\n- `skipOsInstructions` suppresses the base OS prompt while still injecting the generated binding docs.\n\n\u003CCodeSnippet file=\"examples/sessions/client.ts\" region=\"system-prompt\" />\n\n`additionalInstructions` can also be set globally in `agentOs({ options: { additionalInstructions } })` so it applies to every session.","src/content/docs/docs/system-prompt.mdx","4029572852f9303c","docs/versus-sandbox",{"id":274,"data":276,"body":279,"filePath":280,"digest":281,"deferredRender":16},{"title":277,"description":278,"skill":16},"agentOS vs Sandbox","When to use the lightweight agentOS VM, a full sandbox, or both together.","- **agentOS** is a lightweight VM that runs inside your process. Near-zero cold start, low memory, direct backend integration via [bindings](/docs/bindings).\n- **Sandboxes** are full Linux environments with root access, system packages, and native binary support.\n- **You can use both.** agentOS works with sandboxes through [sandbox mounting](/docs/sandbox). Agents run in the lightweight VM by default and spin up a full sandbox on demand.\n\n## Comparison\n\n| | agentOS VM | Full Sandbox |\n|---|---|---|\n| **Cost** | Very low. Runs in your process. | Pay per second of uptime. |\n| **Startup** | Near-zero cold start (~6 ms). | Seconds to spin up. |\n| **Backend integration** | Direct. [Bindings](/docs/bindings) call your functions with zero latency. | Indirect. Requires network calls back to your backend. |\n| **Credentials** | Stay on the host. [Bindings](/docs/bindings) run your functions server-side; agents see only inputs and outputs. | Must be injected into the sandbox environment. |\n| **Permissions** | Granular, deny-by-default. | Coarse-grained (container-level). |\n| **Infrastructure** | `npm install` | Vendor account + API keys. |\n| **Best for** | Coding, file manipulation, scripting, API calls, orchestration. | Browsers, desktop automation, native compilation, dev servers. |\n\n## When to use each\n\n### agentOS VM\n\nUse the lightweight VM for most agent workloads:\n\n- Coding and file editing\n- Running scripts and CLI tools\n- Calling APIs and services via bindings\n- Multi-agent orchestration and workflows\n- Tasks where backend integration matters (permissions, tool access, LLM routing)\n\n### Full sandbox\n\nSpin up a sandbox when the workload needs a real Linux kernel:\n\n- Browsers and desktop automation (Playwright, Puppeteer, Selenium)\n- Heavy compilation and native toolchains\n- Dev servers with hot reload, databases, and system ports\n- GUI applications and VNC sessions\n\n### Both together\n\nUse agentOS with [sandbox mounting](/docs/sandbox) for workflows that need both:\n\n- Agent runs in the agentOS VM with full access to bindings and permissions\n- Sandbox spins up on demand for heavy tasks\n- Sandbox filesystem is mounted into the VM as a native directory\n- Agent reads and writes sandbox files the same way it reads local files","src/content/docs/docs/versus-sandbox.mdx","9bf580251b011be8","docs/webhooks",{"id":282,"data":284,"body":287,"filePath":288,"digest":289,"deferredRender":16},{"title":285,"description":286,"skill":16},"Webhooks","Trigger agent workflows from external webhooks using Hono and queues.","Use a lightweight HTTP server to receive webhooks and drive an agent. This example uses [Hono](https://hono.dev) to receive Slack webhooks and call an agent directly.\n\n## Example: Slack webhook to agent\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/webhooks/server.ts\" />\n\u003C/CodeGroup>\n\n## How it works\n\n1. Slack sends an HTTP POST to `/slack/events`\n2. The Hono handler validates the event and pushes it to the actor's queue\n3. The queue processes messages one at a time, creating agent sessions for each\n4. The agent responds and the worker posts the reply back to Slack\n\nThe queue provides backpressure and durability. If the agent is busy, messages wait in the queue. If the server restarts, queued messages are replayed.\n\n## Recommendations\n\n- Return `200` from the webhook handler immediately after queuing. External services like Slack have short timeout windows.\n- Store webhook secrets in environment variables, not in code.","src/content/docs/docs/webhooks.mdx","94ef640c5f0daa84","docs/workflows",{"id":290,"data":292,"body":295,"filePath":296,"digest":297,"deferredRender":16},{"title":293,"description":294,"skill":16},"Workflow Automation","Orchestrate multi-step agent tasks with durable workflows.","Orchestrate multi-step agent tasks with durable workflows that survive crashes and restarts. Build them with RivetKit's `workflow()` run handler, where each `ctx.step()` is recorded, retried, and resumed independently, and the output of one step can feed into the next.\n\n## Basic workflow\n\nA workflow is the durable `run` handler of an actor. Wrap it in `workflow()` and drive a multi-step agent task as an ordered series of steps: clone the repo, let an agent fix the bug, then run the tests. Trigger work by sending to a queue; the workflow loops and waits durably for the next message.\n\nSession creation and prompting happen within the step that uses them, so a session never has to outlive the work it backs (sessions are ephemeral and would not survive a replay). Steps reach the agentOS VM, a separate actor, through `ctx.client()`.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/workflows/server.ts\" region=\"basic\" title=\"server.ts\" />\n\u003CCodeSnippet file=\"examples/workflows/client.ts\" title=\"client.ts\" />\n\u003C/CodeGroup>\n\n## Agent chaining\n\nOutput of one agent session feeds into the next. Each session is created and completed within its own step, and data passes between steps through the VM filesystem (a review file) and step return values.\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/workflows/server.ts\" region=\"chaining\" title=\"server.ts\" />\n\u003CCodeSnippet file=\"examples/workflows/chaining-client.ts\" title=\"client.ts\" />\n\u003C/CodeGroup>\n\n## Recommendations\n\n- Build the actor's `run` handler with `workflow()` so each `ctx.step()` is durable: recorded, retried, and resumed independently across crashes and restarts.\n- Keep step names stable across code changes. Renaming a step breaks replay for in-progress workflows.\n- Create and close sessions within the step that uses them. Sessions are ephemeral, so keep their lifetime scoped to one unit of work.\n- Pass data between steps via the filesystem or step return values, not session state.\n- Keep `state` changes and other actor-local side effects inside `ctx.step()` callbacks; use non-step workflow code (queue waits, loops, sleeps) only for orchestration.\n- Reach the agentOS VM, a separate actor, from inside a step with `ctx.client()`.\n- See [Workflows](https://rivet.dev/docs/actors/workflows) for the full workflow API reference including timers, joins, and races.","src/content/docs/docs/workflows.mdx","e67a320bd6af7501","docs/agents/claude",{"id":298,"data":300,"body":304,"filePath":305,"digest":306,"deferredRender":16},{"title":301,"description":302,"skill":303},"Claude Code","Run the Claude Code agent inside a VM with skills, MCP servers, and custom configuration.",false,"## Quick start\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/claude/server.ts\" />\n\n\u003CCodeSnippet file=\"examples/claude/client.ts\" title=\"client.ts\" region=\"quickstart\" />\n\u003C/CodeGroup>\n\nRead [Sessions](/docs/sessions) first for session options, streaming events, prompts, and lifecycle management.\n\n## LLM Credentials\n\nSet the relevant variable(s) on the session's `env`, sourced from your server's environment:\n\n- `ANTHROPIC_API_KEY` — Anthropic API key (direct API).\n- `ANTHROPIC_AUTH_TOKEN` — bearer token for proxy / OAuth auth.\n- `ANTHROPIC_BASE_URL` — route through a gateway or proxy endpoint.\n- `ANTHROPIC_MODEL` — override the default model.\n- `CLAUDE_CODE_USE_BEDROCK=1` — use Amazon Bedrock (auth via the AWS credential chain: `AWS_REGION`, `AWS_PROFILE`, …).\n- `CLAUDE_CODE_USE_VERTEX=1` — use Google Vertex AI (auth via Google Cloud credentials).\n\nSee [LLM Credentials](/docs/llm-credentials), and Claude Code's [environment variables](https://code.claude.com/docs/en/env-vars) for the full list.\n\n## Skills\n\nClaude Code discovers [agent skills](https://docs.claude.com/en/docs/claude-code/skills) from `SKILL.md` files under its skills directory. Write the skill into the VM before creating a session and Claude Code loads it automatically.\n\n\u003CCodeSnippet file=\"examples/claude/client.ts\" title=\"client.ts\" region=\"skill\" />\n\n## MCP servers\n\nExpose extra tools to the agent by passing `mcpServers` to `createSession`. Both local child-process servers and remote URLs are supported.\n\n\u003CCodeSnippet file=\"examples/claude/client.ts\" title=\"client.ts\" region=\"mcp\" />\n\n\u003CNote>\n**Pre-install `npx`-launched servers.** A local server started with `npx -y …` writes install progress to **stdout** on its first run, which corrupts the MCP stdio handshake (you'll see `Connection closed`). Pre-install it in the VM so `npx` is silent — `await agent.exec(\"npm install -g @modelcontextprotocol/server-filesystem\")` before the session — or pin the package and point `command` at the installed binary.\n\u003C/Note>\n\n## Customizing the agent\n\nClaude Code is a built-in agent, but it's just a software package under the hood. To ship your own ACP adapter, swap the underlying agent SDK, or register a tweaked build as a new agent, see [Custom Agents](/docs/agents/custom).","src/content/docs/docs/agents/claude.mdx","29e82ba8160cfbd0","docs/agents/codex",{"id":307,"data":309,"body":312,"filePath":313,"digest":314,"deferredRender":16},{"title":310,"description":311,"skill":303},"Codex","Run the Codex coding agent inside a VM with skills, MCP servers, and custom configuration.","## Quick start\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/codex/server.ts\" title=\"server.ts\" />\n\u003CCodeSnippet file=\"examples/codex/client.ts\" region=\"quickstart\" title=\"client.ts\" />\n\u003C/CodeGroup>\n\nRead [Sessions](/docs/sessions) first for session options, streaming events, prompts, and lifecycle management.\n\n## LLM Credentials\n\nSet the relevant variable(s) on the session's `env`, sourced from your server's environment:\n\n- `OPENAI_API_KEY` — OpenAI API key (built-in `openai` provider).\n- `OPENAI_BASE_URL` — route through a gateway or OpenAI-compatible endpoint.\n- Custom providers — defined in `~/.codex/config.toml`; each provider's `env_key` names the variable Codex reads for its key (e.g. `AZURE_OPENAI_API_KEY`, `MISTRAL_API_KEY`).\n\nSee [LLM Credentials](/docs/llm-credentials), and Codex's [config reference](https://developers.openai.com/codex/config-reference) for details.\n\n## Skills\n\nCodex discovers `SKILL.md` files from its skills directory. Write the skill into the VM before creating a session and Codex loads it automatically.\n\n\u003CCodeSnippet file=\"examples/codex/client.ts\" region=\"skills\" />\n\n## MCP servers\n\nExpose extra tools to the agent by passing `mcpServers` to `createSession`. Both local child-process servers and remote URLs are supported.\n\n\u003CCodeSnippet file=\"examples/codex/client.ts\" region=\"mcp\" />\n\n\u003CNote>\n**Pre-install `npx`-launched servers.** A local server started with `npx -y …` writes install progress to **stdout** on its first run, which corrupts the MCP stdio handshake (you'll see `Connection closed`). Pre-install it in the VM so `npx` is silent — `await agent.exec(\"npm install -g @modelcontextprotocol/server-filesystem\")` before the session — or pin the package and point `command` at the installed binary.\n\u003C/Note>\n\n## Customizing the agent\n\nCodex is a built-in agent, but it's just a software package under the hood. To ship your own ACP adapter, swap the underlying agent SDK, or register a tweaked build as a new agent, see [Custom Agents](/docs/agents/custom).","src/content/docs/docs/agents/codex.mdx","b5930de422b103ef","docs/agents/custom",{"id":315,"data":317,"body":320,"filePath":321,"digest":322,"deferredRender":16},{"title":318,"description":319},"Custom Agents","Bring your own coding agent to agentOS by speaking the Agent Client Protocol (ACP) inside the VM.","A custom agent is a program that runs **inside the VM** to drive a coding agent. agentOS spawns it when you call `createSession()` and talks to it over the Agent Client Protocol. You ship it as a software package, exactly like the built-in agents.\n\n## Agent Client Protocol (ACP)\n\nagentOS speaks the [Agent Client Protocol (ACP)](https://agentclientprotocol.com) to every agent: JSON-RPC over stdio. The agent reads protocol messages on **stdin** and writes them on **stdout**, so stdout is reserved for ACP and **stderr is used for logs**. Your program only needs to speak ACP; how it runs the underlying model is up to you. See the [ACP documentation](https://agentclientprotocol.com) for the full protocol.\n\n## Two ways to build an agent\n\nThere are two shapes, depending on whether the agent runs in the ACP process or in its own.\n\n### Single process (embedded)\n\nThe ACP adapter **embeds the agent SDK** and runs it in the same process. One process inside the VM, lower memory footprint.\n\n\u003Csvg viewBox=\"0 0 340 246\" role=\"img\" aria-label=\"Single embedded process: one ACP adapter that embeds the agent, running inside the VM\" style=\"max-width:360px;width:100%;height:auto;display:block;margin:1.25rem auto;font-family:ui-sans-serif,system-ui,sans-serif;\">\n \u003Cdefs>\n \u003Cmarker id=\"ca-arrow-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0 0 L10 5 L0 10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"130\" y=\"18\" width=\"80\" height=\"28\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"170\" y=\"36\" text-anchor=\"middle\" font-size=\"11\" fill=\"#1b1916\">Host\u003C/text>\n \u003Cline x1=\"170\" y1=\"46\" x2=\"170\" y2=\"86\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#ca-arrow-1)\" />\n \u003Ctext x=\"184\" y=\"70\" font-size=\"10\" fill=\"#56524a\">ACP\u003C/text>\n \u003Crect x=\"50\" y=\"90\" width=\"240\" height=\"140\" rx=\"10\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" stroke-dasharray=\"4 3\" />\n \u003Ctext x=\"64\" y=\"110\" font-size=\"11\" fill=\"#56524a\">VM\u003C/text>\n \u003Crect x=\"80\" y=\"135\" width=\"180\" height=\"64\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"170\" y=\"163\" text-anchor=\"middle\" font-size=\"11\" fill=\"#1b1916\">ACP adapter +\u003C/text>\n \u003Ctext x=\"170\" y=\"180\" text-anchor=\"middle\" font-size=\"11\" fill=\"#1b1916\">agent (embedded)\u003C/text>\n\u003C/svg>\n\nFor example, an adapter to run **OpenCode**, which speaks ACP natively. One package is both the ACP process and the agent, so there's no separate adapter and nothing else is spawned.\n\n\u003CCodeSnippet file=\"examples/custom/opencode.ts\" />\n\n### ACP adapter (separate agent)\n\nThe ACP adapter is a thin **bridge** that spawns the real agent as its **own process** (a CLI or SDK) and translates between it and ACP. Full agent feature set, higher memory.\n\n\u003Csvg viewBox=\"0 0 340 276\" role=\"img\" aria-label=\"ACP adapter: a thin adapter inside the VM that spawns the agent as a separate process\" style=\"max-width:360px;width:100%;height:auto;display:block;margin:1.25rem auto;font-family:ui-sans-serif,system-ui,sans-serif;\">\n \u003Cdefs>\n \u003Cmarker id=\"ca-arrow-2\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0 0 L10 5 L0 10 z\" fill=\"#1b1916\" />\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"130\" y=\"18\" width=\"80\" height=\"28\" rx=\"6\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"170\" y=\"36\" text-anchor=\"middle\" font-size=\"11\" fill=\"#1b1916\">Host\u003C/text>\n \u003Cline x1=\"170\" y1=\"46\" x2=\"170\" y2=\"86\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#ca-arrow-2)\" />\n \u003Ctext x=\"184\" y=\"70\" font-size=\"10\" fill=\"#56524a\">ACP\u003C/text>\n \u003Crect x=\"50\" y=\"90\" width=\"240\" height=\"170\" rx=\"10\" fill=\"#faf8f3\" stroke=\"#1b1916\" stroke-width=\"1.5\" stroke-dasharray=\"4 3\" />\n \u003Ctext x=\"64\" y=\"110\" font-size=\"11\" fill=\"#56524a\">VM\u003C/text>\n \u003Crect x=\"90\" y=\"118\" width=\"160\" height=\"42\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"170\" y=\"144\" text-anchor=\"middle\" font-size=\"11\" fill=\"#1b1916\">ACP adapter\u003C/text>\n \u003Cline x1=\"170\" y1=\"160\" x2=\"170\" y2=\"180\" stroke=\"#1b1916\" stroke-width=\"1.5\" marker-end=\"url(#ca-arrow-2)\" />\n \u003Ctext x=\"184\" y=\"174\" font-size=\"10\" fill=\"#56524a\">spawns\u003C/text>\n \u003Crect x=\"90\" y=\"182\" width=\"160\" height=\"46\" rx=\"8\" fill=\"#ffffff\" stroke=\"#1b1916\" stroke-width=\"1.5\" />\n \u003Ctext x=\"170\" y=\"202\" text-anchor=\"middle\" font-size=\"11\" fill=\"#1b1916\">Agent process\u003C/text>\n \u003Ctext x=\"170\" y=\"217\" text-anchor=\"middle\" font-size=\"10\" fill=\"#56524a\">(CLI / SDK)\u003C/text>\n\u003C/svg>\n\nFor example, an adapter to run **Pi**: the `pi` CLI doesn't speak ACP, so `pi-acp` speaks ACP and spawns the CLI as a separate process. The packaged agent bundles both — its `agentos-package.json` names `pi-acp` as the `acpEntrypoint` and points it at the `pi` CLI via `agent.env`.\n\n\u003CCodeSnippet file=\"examples/custom/pi-cli.ts\" />\n\n## Use your agent\n\nRegister the package on the server with `software`. Sessions are then created from the client by `id`, exactly like any built-in agent.\n\n```ts title=\"server.ts\"\nimport { agentOS, setup, defineSoftware } from \"@rivet-dev/agentos\";\n\nconst myAgent = defineSoftware({\n packagePath, // the packed agent .aospkg; its embedded manifest carries the agent block\n});\n\nconst vm = agentOS({ software: [myAgent] });\n\nexport const registry = setup({ use: { vm } });\nregistry.start();\n```\n\nSee [Sessions](/docs/sessions) for creating and driving sessions. Package your adapter with `agentos-toolchain pack --agent my-agent-acp` so its dependencies are bundled into the self-contained package directory and the `agent` block (naming the `bin/` ACP entrypoint) is written into the package's `agentos-package.json`, rather than shipping it as a loose file.\n\nAll built-in agents are defined exactly this way. Browse them for reference on [GitHub](https://github.com/rivet-dev/agentos/tree/main/registry/agent).\n\n## Read more\n\n- [Defining software packages](/docs/custom-software/definition): the full descriptor reference, including the `agentos-package.json` schema and every `agent` field (`acpEntrypoint`, `env`, `launchArgs`, `snapshot`).\n- [Building binaries](/docs/custom-software/building-wasm): compile WASM command binaries and use the registry.\n\n## Debugging\n\nWhen a custom agent exits mid-turn or a tool call fails, capture the agent's stderr with the `onAgentStderr` hook on `AgentOs.create()`. The agent uses stdout for ACP, so stderr carries its logs and crash output. See [Debugging](/docs/debugging) for that hook and the runtime (sidecar) logs.","src/content/docs/docs/agents/custom.mdx","94fea6d6742a9e96","docs/agents/opencode",{"id":323,"data":325,"body":328,"filePath":329,"digest":330,"deferredRender":16},{"title":326,"description":327,"skill":303},"OpenCode","Run the OpenCode coding agent inside a VM with skills, MCP servers, and custom configuration.","## Quick start\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/opencode/server.ts\" />\n\n\u003CCodeSnippet file=\"examples/opencode/client.ts\" title=\"client.ts\" region=\"quickstart\" />\n\u003C/CodeGroup>\n\nRead [Sessions](/docs/sessions) first for session options, streaming events, prompts, and lifecycle management.\n\n## LLM Credentials\n\nOpenCode auto-detects a provider when its key is present on the session's `env`, sourced from your server's environment. Common variables:\n\n- `ANTHROPIC_API_KEY` — Anthropic (Claude), the default.\n- `OPENAI_API_KEY` — OpenAI.\n- `OPENROUTER_API_KEY` — OpenRouter.\n- `GEMINI_API_KEY` — Google Gemini.\n- `GROQ_API_KEY` — Groq.\n- …plus Amazon Bedrock, Azure, Google Vertex, and 70+ providers via [models.dev](https://models.dev).\n\nSee [LLM Credentials](/docs/llm-credentials), and OpenCode's [providers docs](https://opencode.ai/docs/providers/) for the full list.\n\n## Model configuration\n\nTo pin a specific model — or point a provider at a custom endpoint — write an OpenCode config file into the VM before creating the session. OpenCode reads `\u003CHOME>/.config/opencode/opencode.json` (the agent's `HOME` is `/home/agentos` by default).\n\n\u003CWarning>\nTwo settings will silently produce an **empty response** if wrong:\n- The Anthropic provider **`baseURL` must end in `/v1`** (`https://api.anthropic.com/v1`). Without `/v1`, OpenCode calls `…/messages` and Anthropic returns `404 Not Found`.\n- The **`model` must be a current model id.** A retired id returns a `404 not_found_error` and the turn ends with zero output.\n\u003C/Warning>\n\n```ts\n// Write the config before creating the session\nawait agent.mkdir(\"/home/agentos/.config/opencode\", { recursive: true });\nawait agent.writeFile(\n \"/home/agentos/.config/opencode/opencode.json\",\n JSON.stringify({\n $schema: \"https://opencode.ai/config.json\",\n model: \"anthropic/claude-haiku-4-5-20251001\", // use a current model id\n provider: {\n // The Anthropic baseURL MUST include /v1, or requests 404.\n anthropic: { options: { baseURL: \"https://api.anthropic.com/v1\" } },\n },\n }),\n);\n\nconst session = await agent.createSession(\"opencode\", {\n env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },\n});\n```\n\n## Skills\n\nOpenCode discovers `SKILL.md` files from its skills directory. Write the skill into the VM before creating a session and OpenCode loads it automatically.\n\n\u003CCodeSnippet file=\"examples/opencode/client.ts\" region=\"skills\" />\n\n## MCP servers\n\nExpose extra tools to the agent by passing `mcpServers` to `createSession`. Both local child-process servers and remote URLs are supported.\n\n\u003CCodeSnippet file=\"examples/opencode/client.ts\" region=\"mcp\" />\n\n\u003CNote>\n**Pre-install `npx`-launched servers.** A local server started with `npx -y …` writes install progress to **stdout** on its first run, which corrupts the MCP stdio handshake (you'll see `Connection closed`). Pre-install it in the VM so `npx` is silent — `await agent.exec(\"npm install -g @modelcontextprotocol/server-filesystem\")` before the session — or pin the package and point `command` at the installed binary.\n\u003C/Note>\n\n## Customizing the agent\n\nOpenCode is a built-in agent, but it's just a software package under the hood. To ship your own ACP adapter, swap the underlying agent SDK, or register a tweaked build as a new agent, see [Custom Agents](/docs/agents/custom).","src/content/docs/docs/agents/opencode.mdx","055f29f39ce81de3","docs/architecture/agent-sessions",{"id":331,"data":333,"body":336,"filePath":337,"digest":338,"deferredRender":16},{"title":334,"description":335,"skill":16},"Agent Sessions","Internals of agent sessions: how a session is created and bound to a VM, how prompts and events flow from client to sidecar to agent adapter and back, the session lifecycle, and where session state lives.","This page is an internals deep-dive on how agent sessions work under the hood. For the usage API (creating sessions, sending prompts, streaming responses, replaying events), see [Sessions](/docs/sessions).\n\nA session is a long-lived conversation with an agent (such as [Pi](https://github.com/mariozechner/pi-coding-agent)) running inside a VM. Where a bare `exec()` / `run()` starts a fresh guest process and returns when it exits, a session keeps an agent process alive across many prompts, streams its output back as events, and persists a transcript that survives sleep/wake cycles. Everything below describes the machinery that makes that possible while keeping the agent inside the same isolation boundary as any other guest.\n\n## Where a session sits in the component model\n\nThe [three components](/docs/architecture#the-big-picture) are unchanged for sessions: a trusted **client**, the trusted **sidecar** that owns the kernel, and the untrusted **executor** that runs guest code. An agent session adds one more layer on the guest side of the boundary:\n\n- **Client.** Calls `createSession`, `sendPrompt`, and the rest of the session API. It never runs the agent itself; it drives the session over the wire protocol.\n- **Sidecar / kernel.** Spawns the agent as a kernel-managed process inside the VM, owns the session's I/O, applies the permission policy on every syscall the agent makes, and persists the transcript.\n- **Agent adapter.** A per-agent-type shim, inside the VM, that translates between the session protocol and the specific agent's native interface. It normalizes the agent's output into the [Agent Communication Protocol (ACP)](/docs/sessions) so every agent type produces the same event shape.\n- **Executor.** The agent process itself plus any tools it spawns. It is untrusted guest code like any other: its file reads, child processes, and network calls all flow through the kernel.\n\nThe key consequence: an agent is not privileged. It is a guest process that happens to be long-lived and conversational. Its capabilities are exactly the VM's [permission policy](/docs/permissions), nothing more.\n\n## Creating a session and binding it to a VM\n\nA session is always created against an existing VM. The client resolves a VM handle (for example `client.vm.getOrCreate([...])`) and calls `createSession(agentType, options)` on it. Under the hood:\n\n1. **The request crosses the wire** (client to sidecar) carrying the agent type and the session options: `env`, `cwd`, `mcpServers`, `additionalInstructions`, and `skipOsInstructions`.\n2. **The kernel boots the VM** if it is not already running, with its bootstrapped virtual filesystem.\n3. **The sidecar selects the agent adapter** for the requested type and spawns the agent as a kernel-managed process inside that VM. Because the VM does not inherit the host `process.env`, the agent only sees the `env` passed in the options (this is why API keys must be supplied explicitly). The process starts in `cwd` (default `/home/agentos`).\n4. **The session is registered** in the VM with a `sessionId`, and the adapter performs its handshake with the agent to discover `capabilities` and `agentInfo`.\n5. **The handle returns** that metadata to the client.\n\nThe session is bound to that VM for its lifetime. The agent process, its working directory, and its persisted transcript all live inside the VM's isolation domain. A VM can host several sessions at once: each gets its own agent process, but they share the one VM's filesystem (see [Multiple sessions](/docs/sessions#multiple-sessions)). Two sessions in two different VMs share nothing, exactly as described in the [isolation model](/docs/architecture).\n\n\u003CNote>MCP servers configured on a session follow the same boundary. A `local` MCP server runs as a child process inside the VM (kernel-managed, gated by the permission policy); a `remote` MCP server is reached over the network, so its traffic flows through the kernel socket table and is subject to the network allowlist.\u003C/Note>\n\n## How a prompt flows: client to agent and back\n\nSending a prompt is a request in one direction with a stream of events flowing back in the other. The lifecycle of a single prompt extends the general [lifecycle of a request](/docs/architecture):\n\n1. **The client calls `sendPrompt(sessionId, text)`.** The request crosses the wire to the sidecar (hop one).\n2. **The sidecar routes it to the session's agent adapter,** which translates the prompt into the agent's native input and writes it to the running agent process.\n3. **The agent works the turn.** As it thinks, calls tools, edits files, and produces output, every action it takes is a guest syscall back into the kernel (hop two): file reads/writes hit the VFS, tool subprocesses are kernel-managed, and network calls go through the socket table under the allowlist.\n4. **The adapter normalizes the agent's output into ACP events.** Each event is assigned a monotonically increasing sequence number, appended to the session's event log, and persisted.\n5. **Events stream back to the client** as `sessionEvent` notifications, carrying the `sessionId` and the ACP `event` (its `method` and `params`). This is why the docs recommend subscribing to `sessionEvent` before calling `sendPrompt`: events emitted early in the turn would otherwise be missed.\n6. **The turn resolves.** When the agent finishes the turn, `sendPrompt` resolves with the reply.\n\n`cancelPrompt(sessionId)` interrupts an in-progress turn: the request crosses to the sidecar, which signals the agent process to stop the current turn through the adapter, leaving the session itself alive for the next prompt.\n\n```\nclient sidecar / kernel agent adapter agent (executor)\n | sendPrompt | | |\n | --------------------> | route to session | |\n | | ---------------------------> | native input ---> |\n | | | | (thinks, calls\n | | \u003C--- syscalls (VFS, procs, sockets) ------------ | tools, edits)\n | | | \u003C-- native output |\n | sessionEvent (ACP) | persist + assign seq | |\n | \u003C------------------- | \u003C-------- ACP events ------- | |\n | ...stream... | | |\n | resolve(reply) | | |\n | \u003C------------------- | | |\n```\n\n## Session lifecycle\n\nA session moves through these states, all driven by client calls over the wire:\n\n- **Active.** Created and bound to a running VM, with a live agent process. Prompts can be sent and events stream back.\n- **Suspended.** When the VM sleeps, the agent process is torn down but the session's persisted transcript remains in storage. `resumeSession(sessionId)` reconnects: the kernel wakes the VM, re-spawns the agent, and rebinds the session so prompts can continue.\n- **Closed.** `closeSession(sessionId)` gracefully shuts down the agent process and releases its in-VM resources, but leaves the persisted transcript intact so history can still be queried.\n- **Destroyed.** `destroySession(sessionId)` removes the session and all of its persisted events. This is irreversible.\n\nBecause the transcript is persisted, closing or suspending a session is not the same as losing it: the event history can be read back later, even when the VM is not running.\n\nRuntime configuration (`setModel`, `setMode`, `setThoughtLevel`) mutates an active session in place by sending the change through the adapter to the live agent, without restarting the session.\n\n## Resuming a suspended session\n\nWhen a VM sleeps the agent process is destroyed, but the session registry and the transcript survive in SQLite. `resumeSession(sessionId)` (or simply prompting a suspended session) rebinds the stable, client-facing `sessionId` to a freshly spawned agent. Resume is **lazy** (it runs on the first prompt to a non-live session) and **capability-driven** (the orchestrator never special-cases an agent by name, only by what it advertises). There are two paths:\n\n- **Native ACP resume (optimization).** If the agent advertises ACP `loadSession`/`resume` and its own store survived on the durable root, the sidecar issues `session/load` and the agent restores its full context itself. The `sessionId` is unchanged.\n- **Universal transcript fallback.** If the agent has no native resume, or its store did not survive, the sidecar reconstructs a Markdown transcript from the recorded events, writes it into the VM (for example `/root/.agentos/threads/\u003CsessionId>.md`), starts a fresh agent, and prefixes the next prompt with a pointer to that file. Because this needs only file-read tools it works for any agent with no per-agent code, at the cost of pointing the agent at the transcript rather than pre-loading it into context.\n\nResume depends on a **durable root filesystem**. The RivetKit actor configures one automatically (its SQLite-backed root), so transcript capture and resume work out of the box. A direct `AgentOs` SDK user on the default in-memory root has no durable store: transcript capture is a no-op and context cannot be restored, so configure a durable root explicitly if you need resume outside the actor.\n\n## Adapter crashes and bounded auto-restart\n\nIf the agent process exits without `closeSession()` — any spontaneous exit, including exit code 0 — the sidecar treats it as a crash, not a lifecycle transition. Crashes are detected both mid-request (the exchange loop observes the process exit) and while idle (the next write to the dead adapter fails). The sidecar logs the exit with its code and a stderr tail, emits an `AcpAgentExitedEvent` to the host (`onAgentExit` in the SDK, `agentCrashed` on the actor), and attempts an **in-place restart, bounded to 3 attempts per session**:\n\n- **Native re-attach only.** The restart relaunches the adapter with the exact parameters of the original launch, re-probes capabilities with a fresh `initialize`, and — if the agent advertises `loadSession`/`resume` — re-attaches the **same `sessionId`** via `session/load`. Clients keep their handle and the session stays active. There is deliberately no `session/new` fallback tier here: a fallback would produce a different live session id that an in-place restart cannot remap transparently. That path belongs to lazy resume (above), which owns the external→live remap.\n- **Eviction otherwise.** If the adapter has no native resume capability, the restart fails, or the budget is exhausted, the session record is evicted — the same teardown as before auto-restart existed. The persisted transcript is untouched, so the actor's lazy resume can still recover the conversation on the next prompt.\n- **The interrupted request still fails.** A prompt in flight when the adapter died is never replayed (the turn may have had side effects); its error names the restart outcome so the caller knows whether a retry will succeed.\n\nEach event carries `{ exitCode, restart, restartCount, maxRestarts }`, where `restart` is `\"restarted\"`, `\"unsupported\"`, `\"failed\"`, or `\"exhausted\"`; only `\"restarted\"` leaves the session usable. See [Debugging](/docs/debugging#agent-crashes-onagentexit) for capturing these from the SDK.\n\n## Where session state lives\n\nSession state spans two tiers:\n\n- **In-memory, while the VM runs.** The running agent process holds the live conversation, and the sidecar keeps the session's recent event log with sequence numbers, the basis for live reconnection: a client tracks the last sequence number it processed and asks for everything after it, so no events are dropped or duplicated across a reconnect.\n- **Persisted in SQLite, independent of the VM.** Every ACP event is written to a SQLite-backed transcript store inside the VM, keyed by `sessionId` and sequence number. This tier survives sleep/wake and VM shutdown. `listPersistedSessions()` and `getSessionEvents(sessionId)` read from it and work even when the VM is not running, which is what makes transcript-history UIs possible without keeping a VM warm.\n\nSee [Replay events](/docs/sessions#replay-events) for replaying a session's persisted events.\n\nThe transcript living inside the VM keeps session state on the same side of the boundary as the agent that produced it: it is part of the VM's isolation domain, not the client's. The client only ever sees it by asking the sidecar for it over the wire.\n\n## Where to go next\n\n- [Sessions](/docs/sessions): the usage API for creating sessions, sending prompts, and replaying events.\n- [Architecture](/docs/architecture): the component model, request lifecycle, and isolation model that sessions build on.\n- [Permissions](/docs/permissions): the policy the kernel enforces on every syscall an agent makes.\n- [Replay events](/docs/sessions#replay-events): in-memory versus persisted event replay.","src/content/docs/docs/architecture/agent-sessions.mdx","6f588d17c4849062","docs/architecture/agent-sdk-snapshots",{"id":339,"data":341,"body":344,"filePath":345,"digest":346,"deferredRender":16},{"title":342,"description":343,"skill":16},"Agent SDK Snapshots","How an agent's SDK is evaluated once per sidecar into a V8 heap snapshot and reused across sessions instead of re-imported on every createSession: the bundle, the userland snapshot, the process-wide cache, pre-warm, per-session restore, isolation, and the snapshot-safety rules an SDK must follow.","This page is an internals deep-dive on **agent SDK snapshotting** — an optional optimization that loads an agent's SDK *once per sidecar* and reuses it for every session, instead of re-evaluating the whole SDK module graph on each `createSession`. For the agent-author view (how to opt in and the rules your SDK must follow), see [Software Definition → SDK snapshotting & snapshot-safety](/docs/custom-software/definition). For how sessions work in general, see [Agent Sessions](/docs/architecture/agent-sessions).\n\n## The problem: per-session SDK re-evaluation\n\nWhen a session starts, its agent adapter runs inside a fresh [V8 isolate](/docs/architecture/agent-sessions) and imports the agent SDK (for Pi, `@mariozechner/pi-coding-agent`). Importing a real-world SDK means resolving, loading, compiling, and **evaluating** a large module graph — hundreds of modules running their top-level initialization. That evaluation dominates session-creation latency, and because every session gets a fresh isolate, it is paid *again on every `createSession`*.\n\nThe work is identical every time: the same modules, evaluated to the same post-init heap, only to be thrown away when the session ends. Snapshotting captures that post-init heap once and stamps it into every new isolate.\n\n## How V8 heap snapshots work here\n\nagentOS already boots every guest isolate from a **V8 startup snapshot** of the runtime bridge (the polyfill layer that provides `fetch`, node builtins, the kernel-backed module loader, etc.). A startup snapshot is a serialized image of a V8 heap *after* some code has run; restoring it into a fresh isolate reproduces that heap by deserialization rather than re-execution, and each restore produces an independent context.\n\nAgent SDK snapshotting extends that same mechanism: it evaluates the agent SDK **into the same snapshot context, right after the bridge**, so the captured image contains the bridge *and* the fully-initialized SDK. Restoring it gives a fresh isolate where the SDK is already present — no import, no evaluation.\n\n## Where it sits in the component model\n\nThe [three components](/docs/architecture#the-big-picture) are unchanged. Snapshotting only changes *how* the executor's isolate is seeded:\n\n- **Client.** Builds the SDK bundle at package-build time and passes it to the sidecar as trusted VM configuration (`jsRuntime.snapshotUserlandCode`). It decides which agents opt in.\n- **Sidecar.** Owns the snapshot. It builds the snapshot once, caches it process-wide, and seeds each session's isolate from it. The SDK runs inside the isolate under the same [permission policy](/docs/permissions) as any guest code — snapshotting grants the SDK no extra capability.\n- **Executor.** The agent adapter restores into an isolate where the SDK is already on the global, reads it, and proceeds. It is untrusted guest code as always.\n\n## The pipeline\n\n### 1. Bundle the SDK to one snapshottable unit\n\nThe agent SDK and its transitive dependencies are bundled (esbuild, IIFE) into a single file shipped with the agent package (`dist/sdk-snapshot.js`). The bundle evaluates the SDK and publishes its public API on a well-known global (e.g. `globalThis.__PI_SDK_RUNTIME__`). Node builtins stay external (resolved by the bridge's in-snapshot polyfills); heavy provider SDKs that are only reached via dynamic `import()` stay lazy and load post-restore from the VFS.\n\n### 2. Capture the evaluated SDK into the snapshot\n\nThe sidecar runs the bridge, then the SDK bundle, in one snapshot-creation context, then serializes the heap. The result is a startup blob containing both. Per-session configuration (cwd, model, API keys) is **not** captured — it is injected after restore, so one blob serves every session.\n\n### 3. Cache it process-wide, keyed by content\n\nThe blob is stored in a **sidecar-process-wide cache keyed by `sha256(bridge + bundle)`**. Any change to the bridge or the bundled dependency graph changes the key and triggers exactly one rebuild; an unchanged bundle is a cache hit. This is what makes it **build-once-per-sidecar**: the cache is shared across every VM and session in the process.\n\n### 4. Pre-warm so the first session is warm\n\nBuilding the snapshot is itself the expensive evaluation, just done once. To keep it off the session-create critical path, the sidecar **pre-warms** at VM creation: when a VM is configured with a snapshot bundle, the sidecar builds the blob into the cache *before* the first session is created. The first session then restores from a warm cache like every session after it.\n\n\u003CWarning>\n**Pre-warm and V8 initialization order**\nThe V8 platform must be initialized on a long-lived thread *before* any pre-warm runs. agentOS initializes the embedded runtime on the sidecar's main thread at startup. Initializing V8 lazily on a transient worker thread that then exits corrupts the platform and wedges later isolate creation — so the startup init is load-bearing, not incidental.\n\u003C/Warning>\n\n### 5. Restore a fresh isolate per session\n\nOn `createSession`, the agent's isolate is created from the cached blob. The SDK is already evaluated in the snapshot's default context, and each session gets a **fresh context cloned from it**. The adapter reads the SDK off the global instead of importing it. If no snapshot is configured — or snapshot creation fails — the adapter transparently falls back to the per-session dynamic-import path, so snapshotting never affects correctness, only latency.\n\n## Isolation\n\nEach session leases a **fresh context** cloned from the snapshot's default context. The captured SDK is shared read-only through the blob, but live state is not: a global, a captured-object mutation, or even a built-in prototype change made in one session is **not** observable in another. This is the same isolation guarantee as a non-snapshot session — a fresh isolate per session — and is covered by dedicated tests (a session that mutates `globalThis`, an SDK object, and `Array.prototype`, with a second session from the same snapshot seeing none of it).\n\n## Snapshot-safety: what an SDK must avoid\n\nA startup snapshot can only capture a pure JS heap. The SDK's **module-initialization** code (everything that runs at import time, before any function is called) must not, at top level:\n\n- Create a **native handle** — load a `.node` addon, instantiate WebAssembly, or produce any V8 *External* object (for example an ICU-backed `Intl.Segmenter` singleton). These cannot be serialized and abort snapshot creation.\n- Open an **fd, socket, timer, or worker**, or leave a **pending promise** at the end of evaluation.\n- Read **non-deterministic or per-session state** (`process.env`, cwd, model, `Date.now()`, `Math.random()`, a random UUID) into a module constant — it would be frozen to the build-time value.\n\nReal-world SDKs frequently break these by accident. agentOS makes such an SDK snapshottable with **build-time transforms in the bundle** that defer the offending work to first use (e.g. wrap a module-level native singleton in a lazy proxy, inline a top-level config-file read, convert eager fire-and-forget imports to synchronous module references). Each transform asserts the source shape it expects, so an upstream SDK change surfaces as a build error rather than a silent regression. The full author-facing rules live in the [Software Definition](/docs/custom-software/definition) reference.\n\n## Opt-in, per agent\n\nSnapshotting is **opt-in per agent**, via `agent.snapshot: true` on the [agent software descriptor](/docs/custom-software/definition), and requires the agent package to ship a snapshot-safe `dist/sdk-snapshot.js`. Today only the Pi agent opts in; other agents run the normal per-session import path. An agent qualifies by (1) being snapshot-safe and (2) building the bundle — there is nothing Pi-specific in the runtime mechanism.\n\n\u003CNote>\n**Current trade-off**\nThe bundle is currently delivered inline in the (trusted) VM config, which moves bytes onto VM creation. The intended refinement is to ship the bundle as a build-time blob the sidecar loads from the guest VFS by path, keeping the config small. Either way the snapshot is built once per sidecar and reused across sessions.\n\u003C/Note>","src/content/docs/docs/architecture/agent-sdk-snapshots.mdx","eac9412e8bdd154e","docs/architecture/compiler-toolchain",{"id":347,"data":349,"body":352,"filePath":353,"digest":354,"deferredRender":16},{"title":350,"description":351,"skill":16},"Compiler Toolchain","How agentOS compiles its command suite to WebAssembly: Rust coreutils via cargo and C programs via wasi-sdk, linked against a patched wasi-libc plus the wasi-ext bindings, and how the resulting .wasm files become the guest's commands.","The commands a guest runs through [process execution](/docs/processes), the shell\n(`sh`) and the coreutils behind it, are not native host binaries. They are\nWebAssembly modules compiled ahead of time and mounted into the VM. This page\ncovers how that command suite is produced: which toolchains compile it, what it\nlinks against, and how the resulting `.wasm` files become the guest's commands.\n\nFor *why* WASM is a first-class guest and *how* it presents a POSIX surface at\nruntime, see the [WASM VM](/docs/architecture/posix-syscalls) page. This page is the build-side\ncounterpart: it documents the toolchain that emits binaries carrying both\n[the host-import layer and the WASI shim](/docs/architecture/posix-syscalls).\n\n## Target: `wasm32-wasip1`\n\nEverything in the command suite is compiled to a single target,\n`wasm32-wasip1`: the WASI preview 1 ABI on the 32-bit WebAssembly architecture.\nPicking one target for the whole suite means a single libc, a single set of\nhost import declarations, and a single runtime shim can serve every command.\n\nA guest module built for this target expects standard WASI (preopened file\ndescriptors, clocks, randomness, file I/O) plus the extra agentOS import modules\ndescribed below. Both halves are satisfied at runtime by the kernel-backed\nruntime; nothing in a compiled command reaches a real host syscall.\n\n## Two source languages, two compilers\n\nThe suite is heterogeneous: most tools are Rust, some are C. Each language uses\nits own compiler driver, but both emit the same `wasm32-wasip1` ABI and link\nagainst the same sysroot, so the outputs are interchangeable at runtime.\n\n- **Rust coreutils** are built with **`cargo`** targeting `wasm32-wasip1`. Rust's\n standard library already has first-class support for this target, so the\n coreutils crates compile with an ordinary cross-compile invocation.\n- **C programs** are built with the **`wasi-sdk`** toolchain, a packaged\n `clang` plus sysroot tuned for WASI. C tools that have no Rust equivalent (or\n that are easier to carry as upstream C) go through this path.\n\n```bash\n# Rust coreutils\ncargo build --target wasm32-wasip1 --release\n\n# C programs via wasi-sdk, linked against the patched libc + wasi-ext\n$WASI_SDK/bin/clang --target=wasm32-wasip1 \\\n --sysroot=$WASI_SYSROOT \\\n -lwasi-ext \\\n tool.c -o tool.wasm\n```\n\n## What every binary links against\n\nRegardless of source language, each command links against the same two pieces.\nTogether they give a single binary both the standard WASI calls and the agentOS\nprocess / user / network extensions.\n\n- **A patched `wasi-libc`.** The libc is the WASI standard library, modified so\n that the calls a normal command-line program performs resolve against the\n agentOS surface instead of failing or hitting unimplemented stubs. This is the\n same patched libc the [Layer 2 shim](/docs/architecture/posix-syscalls) adapts at runtime; the\n build side and the runtime side are two ends of the same contract.\n- **The `wasi-ext` bindings.** These declare the extra WebAssembly import\n modules (`host_process`, `host_user`, `host_net`, and the small\n `host_sleep_ms` binding) that base WASI cannot express. Linking `wasi-ext`\n into a binary is what lets its libc emit `fork` / `exec`, `getuid` / `getgid`,\n and `connect` / `listen` as ordinary-looking syscalls that the host runtime\n then services through the kernel. See\n [Layer 1: custom host import modules](/docs/architecture/posix-syscalls) for the runtime half.\n\n\u003CNote>\nThe import declarations are compile-time only: linking `wasi-ext` tells the\nmodule *which* host imports to reference, but the calls are still routed through\nthe kernel and gated by the VM's [permission policy](/docs/permissions) at\nruntime. Building against `host_net` does not grant network access.\n\u003C/Note>\n\n## From `.wasm` to a guest command\n\nThe compiler toolchain's product is a set of `.wasm` files, one per command.\nThose files are what the runtime mounts as the guest's executables: when a guest\ninvokes `ls`, `sh`, or any other bundled tool, the kernel resolves the name to\nthe corresponding module, instantiates it with the host imports and the WASI\nshim wired in, and runs it as a [child process](/docs/processes) with real\nprocess, user, and network semantics, all virtualized.\n\nThe same path is open to your own programs. A program you compile for\n`wasm32-wasip1` runs as a guest command exactly like the bundled ones; link the\n`wasi-ext` bindings if it needs processes, users, or sockets, and leave them out\nfor a pure-compute tool. Heavy native binaries that are not yet available as\nWASM belong in a [mounted sandbox](/docs/sandbox) instead.\n\n## Recommendations\n\n- Use the bundled WASM coreutils and `sh` for normal shell workloads; they\n already carry the patched libc and the `wasi-ext` extensions.\n- To ship your own command, compile it for `wasm32-wasip1` with `cargo` (Rust)\n or the `wasi-sdk` `clang` (C), and link `wasi-ext` only if it needs the\n process / user / network host imports.\n- Keep the build and runtime contracts aligned: the patched `wasi-libc` and the\n `wasi-ext` import declarations a binary is compiled against are the same ones\n the [WASM VM](/docs/architecture/posix-syscalls) runtime expects to satisfy.","src/content/docs/docs/architecture/compiler-toolchain.mdx","49ef24cfa33c5d1f","docs/architecture/filesystem",{"id":355,"data":357,"body":359,"filePath":360,"digest":361,"deferredRender":16},{"title":111,"description":358,"skill":16},"Internals of the kernel VFS: the overlay/mount/root engines, how guest fs syscalls are routed and confined, WASM preopens, and mount confinement against symlink and .. escapes.","This page is an internals deep-dive on the **kernel virtual filesystem (VFS)**: how it is layered, how a guest `fs` syscall is routed through it, and how guest I/O is confined to the VM. For the user-facing API (reading, writing, mounting, persistence), see [Filesystem](/docs/filesystem).\n\nThe invariant this whole subsystem exists to uphold: **every guest filesystem operation is serviced by the kernel-owned VFS, never by a real host capability.** There is no host disk reachable from the guest. The VFS presents normal Linux semantics to tools while keeping every byte inside the kernel.\n\n\u003CNote>The security boundary is sidecar to executor. The VFS lives inside the trusted sidecar; the guest in the executor only ever *asks* for a filesystem operation. Confinement is the kernel's job, not the guest's. See the [Security Model](/docs/security-model) for the full threat model.\u003C/Note>\n\n## Where the VFS sits\n\nA guest `fs` call never touches the host. The path is always:\n\n```\nguest fs call (executor)\n -> kernel syscall (crosses sidecar \u003C-> executor boundary)\n -> VFS engine resolves the path\n -> backing store services the operation\n -> result returns to the executor\n```\n\n- The executor holds **no** filesystem capability of its own. It issues a syscall and blocks for the reply.\n- The kernel checks the applied permission policy for the filesystem scope before servicing the request.\n- The VFS resolves the path against the VM's layered engines, then services the operation against the engine that owns that path.\n\nBecause every byte is mediated here, two properties fall out for free: the guest can never reach the real host disk, and one VM's filesystem is never visible to another VM. Isolation is per-VM.\n\n## The VFS engines\n\nThe per-VM filesystem is not a single flat store. It is a tree of **engines**, each responsible for a subtree of the namespace. A path is resolved by walking from the root engine down to whichever engine owns the deepest matching prefix, then handing the remainder of the path to that engine.\n\n- **Root engine.** Owns `/` and the base namespace. Every VM boots with a root filesystem bootstrapped from a snapshot, so the guest starts against a populated POSIX tree (the default working directory is `/home/agentos`).\n- **Overlay engine.** Composes layers so writes land in a writable upper layer while reads fall through to a lower layer. This is how a read-mostly base can be presented as writable to the guest without mutating the shared lower layer.\n- **Mount engine.** Grafts a distinct backing store onto a guest path (a mount point). Below the mount point, operations are routed to that mount's backend instead of the parent engine. This is the mechanism behind in-memory, host-directory, S3, and Google Drive mounts.\n\nResolution is **longest-prefix wins**: if `/mnt/data` is a mount and the guest opens `/mnt/data/file`, the mount engine services it; anything outside `/mnt/data` stays with the parent (root/overlay) engine.\n\n```\n/ \u003C- root engine (bootstrapped from snapshot)\n|- home/user/... \u003C- root / overlay\n|- mnt/\n| |- scratch/... \u003C- mount engine -> in-memory backend\n| |- code/... \u003C- mount engine -> host-directory backend (read-only)\n| \\- data/... \u003C- mount engine -> S3 backend\n\\- ...\n```\n\nThe base layer is in-memory and per-VM; the runtime transparently persists it to backing storage so it survives sleep/wake. Mounts are pluggable: any guest path can be backed by the host, a remote, or a cloud store. See [Mounting filesystems](/docs/filesystem#mounts) for the user-facing config.\n\n## Routing a guest syscall\n\nWhen the guest calls, say, `readFileSync(\"/mnt/data/report.csv\")`:\n\n1. **Permission check.** The kernel verifies the filesystem scope is granted for that operation. Nothing is bound by default; access is denied until opted in (see [Permissions](/docs/permissions)).\n2. **Engine resolution.** The VFS walks the namespace and selects the engine owning the longest matching prefix (`/mnt/data` -> the S3 mount engine).\n3. **Path normalization and confinement.** The remainder of the path is normalized within the owning engine's root. `.` and `..` segments are resolved *before* the operation reaches the backend, so the request cannot climb above the engine's root.\n4. **Backend operation.** The owning engine's backend services the read/write/stat/etc. against its store (in-memory pages, the persisted base, a host directory, S3, ...).\n5. **Reply.** The result crosses back to the executor, which unblocks.\n\nHost-side APIs (`agent.writeFile`, `agent.readFile`) enter the *same* VFS from the trusted side, which is why the host can seed and read files the guest sees, without ever exposing the real host disk to the guest.\n\n## Mount confinement\n\nA host-backed mount (host directory, S3, ...) comes from trusted config, so its existence, target, and credentials are not attack surface. What *is* in scope is the guest-driven traffic through it: the guest must not be able to use a mounted path to reach bytes outside the mount root. Confinement is enforced by the kernel, on every operation:\n\n- **`..` traversal.** Path segments are normalized relative to the mount root before the backend sees them. A guest path like `/mnt/code/../../etc/passwd` cannot resolve above the mount root; it is clamped to the mount's own subtree (and, above the mount point, handed back to the parent engine, which is itself the kernel VFS, not the host).\n- **Symlinks.** Symlink resolution (`realpath` following) is performed by the kernel against the *virtual* namespace, not the host's. A symlink inside a host-directory mount cannot be used to escape the mount root onto the wider host filesystem; the resolved target is re-confined to the mount root.\n- **Path aliasing / TOCTOU.** Because resolution and confinement happen inside the kernel on each operation, there is no window where the guest resolves a path and the backend later acts on a different one. The guest sees only the mounted subtree, never the wider host filesystem.\n\nMounts for host and remote backends are **read-only by default**; a writable mount must be opted into explicitly. The `readOnly` flag is enforced at the engine, so a write syscall to a read-only mount fails inside the kernel rather than reaching the backend.\n\n\u003CNote>\"Trusted mount, untrusted traffic\": the mount's target is trusted configuration, but the guest drives I/O through it, so confining guest operations to the mount root (`..`, symlink, TOCTOU, path-aliasing) is squarely in scope and enforced by the kernel.\u003C/Note>\n\n## WASM preopens\n\nWASI does not grant a WASM guest an ambient filesystem. Instead, the host hands the module a set of **preopened directories**: capability handles to specific subtrees, and the guest can only reach paths reachable from a preopen.\n\nIn agentOS these preopens are wired to the **same kernel VFS** rather than to host directories:\n\n- A preopen maps a guest-visible path to a VFS subtree. File descriptors derived from it are serviced by the VFS engines above, with the same confinement rules.\n- The WASM guest therefore sees the virtualized filesystem (root snapshot, overlays, mounts) through standard WASI calls, with no host filesystem handle anywhere in the chain.\n- Confinement composes: a preopen rooted at a mount point inherits that mount's `..`/symlink confinement, because resolution still runs through the kernel VFS.\n\nThe result is that WASI filesystem access and the V8/Node `fs` path converge on one virtual filesystem, so both executor flavors get identical isolation and identical Linux semantics.\n\n## Where to go next\n\n- [Filesystem](/docs/filesystem): the user-facing API for reading, writing, mounting, and persistence.\n- [Architecture](/docs/architecture): the components, trust boundary, and kernel-owned syscall paths.\n- [Permissions](/docs/permissions): the filesystem scope the kernel checks on every operation.\n- [Security Model](/docs/security-model): the full trust model and threat boundary.","src/content/docs/docs/architecture/filesystem.mdx","6611b9713df06bfc","docs/architecture/limits-and-observability",{"id":362,"data":364,"body":367,"filePath":368,"digest":369,"deferredRender":16},{"title":365,"description":366,"skill":16},"Limits & Observability","How agentOS bounds resources, applies backpressure, warns before a limit is hit, and surfaces it all to the host.","agentOS runs untrusted, AI-generated code inside disposable VMs. Every resource\nthat code can consume is **bounded by default**, and every bound is designed to\n**warn before it is hit**, **fail with a clear error when it is**, and stay\n**inspectable** from one place. This page explains how the limits, backpressure,\nlogging, and observability pieces fit together across the stack.\n\n## Where limits live\n\nLimits are owned by **secure-exec** (the VM runtime) and **forwarded** by agentOS\n— the agentOS layer does not reimplement enforcement, it exposes the knobs and\nsurfaces the signals.\n\n| Layer | Responsibility |\n| --- | --- |\n| secure-exec kernel | Enforces per-VM resource caps (memory/heap, CPU time, fds, processes, sockets, filesystem bytes, …). |\n| secure-exec sidecar | Owns the bounded queues between the guest, the runtime, and the host; applies backpressure; tracks usage. |\n| agentOS client | Forwards `limits` config to the VM and surfaces limit signals to the caller. |\n\n## Three guarantees for every limit\n\nEvery bound — a resource cap, a bounded queue, a timeout, a payload size —\nfollows the same contract:\n\n1. **Bounded by default.** Nothing is unbounded out of the box. Memory is capped\n at ~128 MiB per isolate (Cloudflare Workers parity), CPU is bounded, and every\n queue has a fixed capacity. Operators may *raise* a cap, but never get an\n unbounded default.\n2. **Warn on approach.** As usage crosses a threshold (default **≥80%** of\n capacity), a structured warning is emitted — once per crossing, re-armed only\n after it drains back below 50% (hysteresis), so a busy limit logs once, not on\n every operation.\n3. **Clear, typed error on breach.** Exceeding a limit produces an error that\n names the limit, the observed-vs-cap value, and the config path to raise it —\n never a bare `EAGAIN`, a silent drop, or a crash.\n\n## Backpressure, not catastrophe\n\nThe path from guest code to the host is a **chain of bounded queues**: the V8\nruntime → a per-session frame channel → the V8→host event channel → the sidecar\nstdout frame queue → the host. When a queue fills (a slow host consumer, a chatty\ntool turn), the producer **blocks until the consumer drains a slot** — clean\nbackpressure that flows all the way back to the guest. A full queue never\ndestroys the session, silently drops data, or crashes the sidecar; a genuinely\ndead consumer surfaces as a typed terminal error instead.\n\nBuffer capacities are sized so that *transient* bursts are absorbed without ever\nengaging backpressure; backpressure is the safety net for a genuinely stuck\nconsumer, not a normal-operation event.\n\n## The limit registry\n\nAll bounded limits register with a single in-process **limit registry**. Each\nregistered limit tracks its live depth, high-water mark, and capacity, and emits\nthe near-capacity warning described above. This gives the runtime one place to\nanswer two questions:\n\n- *Is a limit about to be hit?* — the registry fires the approach warning.\n- *What is the current usage of everything?* — a registry snapshot lists every\n limit's depth / high-water / capacity / fill-percent for debugging.\n\nA CI audit fails the build if any limit-shaped constant is not classified and —\nfor operator-tunable ones — wired to a config field, so \"is everything bounded\nand config-wired?\" is verified mechanically rather than by review.\n\n## Logging & host visibility\n\nsecure-exec logs to **stderr** (never stdout — stdout is the framed wire\nprotocol). The default level is `WARN`, tunable with the `AGENTOS_LOG`\nenvironment variable (`error` to quiet, `debug` for per-limit usage snapshots).\nNear-limit warnings and backpressure events therefore show up in the sidecar's\nstderr stream, which agentOS forwards to the host.\n\nThe limit registry also exposes a structured **warning sink**: a callback that\nfires on the same edge as the log, carrying `{ name, category, observed,\ncapacity, fillPercent }`. This is the foundation for host-facing limit\nobservability — a structured \"a limit is approaching capacity\" signal rather than\na parsed log line.\n\n## See also\n\n- [Resource Limits](/docs/resource-limits) — the full `limits` config surface.\n- [Processes](/docs/architecture/processes) and [Sessions & Persistence](/docs/architecture/sessions-persistence) — the layers the queue chain runs through.","src/content/docs/docs/architecture/limits-and-observability.mdx","451a3f1e63bd8857","docs/architecture/networking",{"id":370,"data":372,"body":375,"filePath":376,"digest":377,"deferredRender":16},{"title":373,"description":374,"skill":16},"Networking","How the kernel socket table works: a single VM-local transport that carries host, JavaScript, and WASM traffic, where fetch / net / dns route through it, how egress policy and loopback confinement are enforced, and how preview URLs are served.","This is the internals view of agentOS networking: the kernel socket table, the layers a request crosses, and where policy is enforced. For the user-facing API (`vmFetch`, preview URLs, the confinement model from a caller's perspective), see [Networking & Previews](/docs/networking). For the trust boundary this all sits inside, see [Architecture](/docs/architecture).\n\nThe governing rule is that there is exactly **one authoritative transport for everything VM-local**: the kernel socket table. No part of guest networking opens a real host socket on its own. Guest `fetch()`, `node:http`, `node:net`, WASM TCP clients and servers, and host-into-guest requests (`vmFetch` / `rt.fetch`) all target the same listener table.\n\n## The kernel socket table\n\nThe socket table is the floor of the stack and the only component that actually moves bytes between two in-VM endpoints. It is per VM, so two VMs never share a listener or a connection.\n\n- It exposes POSIX-style primitives: `socket_create`, `socket_bind_inet`, `socket_connect_inet_loopback`, `socket_read`, `socket_write`, `poll_targets`.\n- Every call is **owner-checked** (the calling process must own the descriptor) and **resource-accounted** against the VM's limits.\n- Failures return correct POSIX errnos (`ECONNREFUSED`, `EACCES`, …) so guest code branches the way it would on real Linux.\n- Connecting pairs two in-VM sockets and shuttles bytes between them. No host networking happens at this layer.\n\nBecause every server is a kernel TCP listener, a client never needs to know whether the server it is talking to is JS, WASM, raw TCP, or HTTP. HTTP is layered on top of kernel TCP bytes, so every listener lives in the one table and is reachable identically.\n\n\u003CNote>An earlier design carried two listener models at once: stream-mode listeners (`net.createServer`, WASM) on real kernel TCP sockets, and object-mode HTTP listeners (`http.createServer`) on a separate table that exchanged JSON request/response objects over stream events. A second guest process could not reach the object-mode table reliably, because the client expected byte-stream TCP semantics while the server only spoke object-mode dispatch. The current architecture removes the second model: everything is one socket table.\u003C/Note>\n\n## The four layers\n\nA request passes through four layers. Only the top and bottom understand HTTP; the middle two move bytes and enforce policy.\n\n| Layer | Role | Trust | Lives in |\n| --- | --- | --- | --- |\n| 4 · Guest bridge | `node:http` / `node:net` / `fetch` / undici shim | untrusted (V8 isolate) | `crates/execution/assets/v8-bridge.source.js` |\n| 3 · Sync-RPC dispatch | routes `net.connect`, `net.http_request`, `net.listen`, … | trusted | `crates/sidecar/src/service.rs` |\n| 2 · Execution & enforcement | listener state, host fetch client, permission checks | trusted (TCB) | `crates/sidecar/src/execution.rs` |\n| 1 · Kernel socket table | `bind` / `listen` / `connect` / `read` / `write`, loopback routing | trusted (TCB floor) | `crates/kernel/src/socket_table.rs`, `kernel.rs` |\n\n### Layer 1: kernel socket table\n\n`crates/kernel/src/kernel.rs` exposes the primitives above. Loopback routing is the heart of VM-local networking: `socket_connect_inet_loopback` only succeeds against a socket that is actually bound and listening in the same VM's table; otherwise it returns `ECONNREFUSED`. Resource-limit checks run before the two sockets are paired.\n\n### Layer 2: sidecar execution (enforcement point / TCB)\n\n`crates/sidecar/src/execution.rs` is where policy is applied. Two roles matter for networking:\n\n- **Listener state.** `build_javascript_socket_path_context` walks every active process and records what is listening on which port, including a map of HTTP loopback targets keyed by `(family, port)`. This is the source of truth a connect consults to learn that, say, \"port 3000 is an HTTP server owned by process X, server Y.\"\n- **Host fetch client.** When the host calls `vmFetch` / `rt.fetch()`, the sidecar resolves the target to a VM-owned kernel listener, opens its own kernel socket, connects over loopback, and speaks HTTP/1.1 to the guest server. This is the only HTTP client that lives in the sidecar (the host has no guest isolate to do framing for it).\n\n### Layer 3: sync-RPC dispatch\n\n`crates/sidecar/src/service.rs` routes the bridge calls guest code makes. The guest-to-guest loopback HTTP path lands here as `net.http_request`. It is the most security-sensitive RPC, so it is guarded in order:\n\n1. The host must be a loopback address.\n2. The applied network policy must permit the operation.\n3. The requested `(process_id, server_id)` must match a listener that is currently live.\n\nThat last check stops a guest from forging a target to reach a process it should not.\n\n### Layer 4: guest bridge\n\n`crates/execution/assets/v8-bridge.source.js` is the Node-compatibility shim inside the untrusted V8 isolate. It presents `node:http`, `node:net`, `fetch`, and undici to guest code and translates them into Layer 3 bridge calls. `http.createServer()` is implemented on top of `net.Server`: each accepted byte socket is parsed as HTTP and dispatched to the guest's request handler.\n\n## How fetch, net, and dns route through it\n\n- **`node:net` (raw TCP).** `net.connect` / `net.createServer` map directly onto kernel `connect` / `bind` + `listen`. The bytes are the payload; no framing is added.\n- **`node:http` and `fetch`.** A guest HTTP server is a `net.Server` whose accepted sockets are HTTP-parsed in the bridge. A guest HTTP client runs undici over a kernel-backed dispatcher (or a raw serializer for the loopback fast path). Either way the bytes travel as kernel TCP.\n- **DNS.** Name resolution is serviced by the kernel resolver, not the host. Outbound connections that leave the VM resolve through it, and the resolved addresses are then filtered by the egress allowlist (see below). DNS pinning ties the connection to the address that was checked, closing the resolve-then-reconnect TOCTOU gap.\n\n### Where HTTP meets TCP\n\nThere is no shared HTTP/TCP translation module. Because the wire between every endpoint is raw TCP bytes through the kernel, HTTP is framed and deframed **at each edge that speaks HTTP**. The kernel (Layer 1) and the sidecar routing (Layer 2) never parse HTTP. There are three independent codecs, one per kind of endpoint:\n\n| Endpoint | Lives in | Encode / decode |\n| --- | --- | --- |\n| Guest HTTP server | guest bridge | `parseLoopbackRequestBuffer` (bytes to object), `serializeLoopbackResponse` (object to bytes), wired per accepted socket by `attachHttpServerSocket` |\n| Guest HTTP client | guest bridge | undici over a kernel-backed dispatcher, or `serializeRawHttpRequest` + `waitForRawHttpResponse` |\n| Host fetch client | sidecar execution | `serialize_kernel_http_fetch_request` (request to bytes), `parse_kernel_http_fetch_response` (bytes to JSON) |\n\nA WASM HTTP server or client does its own framing in guest code (reading the request line, writing a response with standard C socket calls). The kernel does not help it; it is just bytes, the same as for the JS endpoints.\n\n## Data flows\n\n- **Host to guest (`vmFetch` / `rt.fetch`).** The sidecar resolves the port to a VM-owned kernel listener, opens a sidecar-owned kernel socket, connects over loopback, serializes the request bytes, drives the target process forward so it can accept and respond, then parses the response bytes back into the host response object. It is **fail-closed**: no DNS, no external networking, no host-loopback fallback. If no VM-owned listener exists, it returns a missing-listener error.\n- **Guest to guest.** `net.connect` goes through the sidecar, which returns a loopback HTTP target handle. The guest sends the request through `net.http_request`, which dispatches into the target process's request handler. Cross-process loopback passes through the enforcement point rather than taking an in-isolate shortcut.\n- **Cross-runtime (JS and WASM, either direction).** Client and server connect through a kernel loopback socket pair and exchange raw bytes. JS to WASM, WASM to JS, and WASM to WASM all use the same path; only the side that runs the HTTP codec differs.\n- **Guest outbound to host or external.** Connections that do not target a VM-owned listener take the external network path: permission checks, DNS pinning, then a real host `TcpStream`. Reaching a host loopback port still requires an explicit loopback exemption entry.\n\n## Egress policy and loopback confinement\n\nGuest networking is confined by three distinct controls plus the loopback-only default. The permission policy and limits are **trusted configuration**; the guest executor is the **untrusted subject** they bind.\n\n### Loopback-only by default\n\nGuest listeners are reachable only over loopback (`127.0.0.1` / `::1`) inside the VM.\n\n- Binding to `0.0.0.0` or `::` does not widen this: the kernel normalizes the unspecified address down to loopback, so the listener still answers only on loopback.\n- A connection that originates outside the loopback interface and targets a port the VM does not own is refused with `EACCES`, noting the port is not exempt.\n- This confinement is independent of the permission policy. Even with the network allowed, a guest server stays loopback-only unless its port is explicitly exempted.\n\n### Three stacked controls\n\nThese are often conflated but are separate. They stack, and a request must pass every one that applies:\n\n1. **Permission policy** (`network.listen` / `network.connect`). Decides whether the guest may open a listener or initiate an outbound connection at all. A blocked operation fails with `blocked by network.listen policy` or `blocked by network.connect policy`.\n2. **Loopback confinement.** Decides who may reach an already-permitted guest listener. By default only loopback inside the VM; a per-port exemption loosens it.\n3. **DNS / egress allowlist.** Constrains where permitted outbound connections may go. The kernel filters resolved addresses, blocking outbound access to restricted ranges, so an allowed `connect` can still be refused by destination.\n\nThe per-port loopback exemption belongs to layer 2 only. It is a trusted, per-port whitelist that *loosens* the default loopback confinement (for example, exposing an in-VM dev server beyond loopback). It is not an egress control and grants no outbound reach; layers 1 and 3 still apply. It is configured with `loopbackExemptPorts`, a list of ports that are exempt from the SSRF checks at layer 2; each listed port is reachable from outside the loopback interface, while the permission policy and egress allowlist continue to apply.\n\n### Trust and ownership\n\nEvery guest connect, listen, read, and write passes through sidecar ownership and kernel owner checks. Guest-to-guest loopback is allowed only when the destination is a VM-owned listener and the applied network policy permits the connect. Host-loopback access from guest code is separate and still requires a loopback exemption plus the applied network policy. Long-lived waits must not block the sync-RPC path, so the stack uses stream events, bounded polling, and kernel socket waits with explicit timeouts.\n\n\u003CNote>Host-to-guest requests bypass egress, not the table. `vmFetch` / `rt.fetch` terminate at the guest's loopback listener and never leave the VM, so they work even when guest egress (layer 3) or outbound `connect` (layer 1) is denied. They are host control-plane traffic, not guest egress, and only ever reach VM-owned listeners, while still going through the same kernel socket table as everything else.\u003C/Note>\n\n## Preview URLs\n\nA preview URL is port forwarding for a VM service: a time-limited, signed, publicly reachable URL that proxies HTTP to a port inside the VM. Mechanically it reuses the host-to-guest path:\n\n- A signed token is minted for a `(VM, port)` pair with an expiration, capped by `preview.maxExpiresInSeconds`. Tokens are stored in SQLite, survive sleep/wake cycles, and expired ones are cleaned up automatically.\n- An incoming request to the preview path is authenticated against the token, then proxied into the VM exactly like `vmFetch`: resolve the port to a VM-owned kernel listener, connect over loopback, frame HTTP/1.1, drive the target process, and stream the response back. The same fail-closed, VM-owned-listener-only rules apply.\n- CORS is enabled so browsers can reach preview URLs from any origin.\n- Revocation (`expireSignedPreviewUrl`) invalidates the token immediately, after which the proxy refuses the request before touching the socket table.\n\nBecause previews ride the host fetch path, they are subject to loopback confinement at the kernel but **not** to the guest egress allowlist: the request enters the listener from the host side and never becomes guest outbound traffic.\n\n## Where to go next\n\n- [Networking & Previews](/docs/networking): the `vmFetch` and preview URL API, with usage examples.\n- [Architecture](/docs/architecture): the client / sidecar / executor trust boundary this stack lives inside.\n- [Security Model](/docs/security-model): the full in-scope and out-of-scope threat model.","src/content/docs/docs/architecture/networking.mdx","9a3a667862c77ad4","docs/architecture/posix-syscalls",{"id":378,"data":380,"body":383,"filePath":384,"digest":385,"deferredRender":16},{"title":381,"description":382,"skill":16},"POSIX Syscalls","How agentOS extends WASI in two layers so WebAssembly guests behave like normal POSIX programs on top of the kernel.","Not everything inside an agentOS VM is JavaScript. The shell (`sh`) and the\ncoreutils behind [process execution](/docs/processes) ship as WebAssembly\nbinaries, and you can run your own WASM programs too. To make those programs\nbehave like normal Linux tools, agentOS presents a POSIX syscall surface on top\nof WebAssembly.\n\n- **WASM is a first-class guest.** WASM binaries run beside JavaScript inside the same VM.\n- **Same kernel, same boundary.** WASM syscalls route through the same kernel that backs JS guests, so there is no extra host access.\n- **POSIX shape, not host access.** The extensions below add process, user, and network *semantics*, all virtualized.\n\n## Why WASI alone is not enough\n\nThe base standard for WASM system access is **WASI** (specifically `wasip1`).\nWASI is intentionally minimal:\n\n- It gives a guest preopened file descriptors, clocks, randomness, and basic file I/O.\n- It has **no process model** (no `fork` / `exec` / `wait`).\n- It has **no users or groups** (no `getuid` / `getgid`).\n- It has **no general sockets** (no `connect` / `listen`).\n\nReal command-line programs expect all of those. agentOS closes the gap in two\nlayers, and both route through the kernel rather than the host.\n\n\u003CNote>\nEvery WASM syscall, like every JS syscall, goes through the kernel-owned virtual\nfilesystem, process table, and socket table. The extensions below add POSIX\n*shape*; they do not add host access. See the [Security Model](/docs/security-model)\nfor the isolation boundary.\n\u003C/Note>\n\n## The two-layer model\n\nagentOS layers a POSIX surface over WASM. Layer 1 adds capabilities WASI does\nnot express at all; Layer 2 adapts the standard WASI calls so a normal libc\nbehaves correctly inside the VM. Both bottom out in the kernel.\n\n\u003Csvg viewBox=\"0 0 700 360\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"Two-layer WASM-on-kernel model\" style=\"max-width: 700px; width: 100%; height: auto; font-family: ui-sans-serif, system-ui, sans-serif;\">\n \u003Crect x=\"0\" y=\"0\" width=\"700\" height=\"360\" fill=\"#ffffff\" />\n\n {/* Guest */}\n \u003Crect x=\"40\" y=\"20\" width=\"620\" height=\"56\" rx=\"8\" fill=\"#f4f4f5\" stroke=\"#d4d4d8\" />\n \u003Ctext x=\"350\" y=\"44\" text-anchor=\"middle\" font-size=\"15\" font-weight=\"600\" fill=\"#18181b\">WASM guest (sh, coreutils, your .wasm)\u003C/text>\n \u003Ctext x=\"350\" y=\"64\" text-anchor=\"middle\" font-size=\"12\" fill=\"#52525b\">compiled for wasm32-wasip1, linked against patched wasi-libc\u003C/text>\n\n {/* Layer 1 */}\n \u003Crect x=\"40\" y=\"100\" width=\"300\" height=\"120\" rx=\"8\" fill=\"#eef2ff\" stroke=\"#c7d2fe\" />\n \u003Ctext x=\"190\" y=\"124\" text-anchor=\"middle\" font-size=\"14\" font-weight=\"600\" fill=\"#3730a3\">Layer 1: host import modules\u003C/text>\n \u003Ctext x=\"190\" y=\"148\" text-anchor=\"middle\" font-size=\"12\" fill=\"#3730a3\">host_process — spawn / wait\u003C/text>\n \u003Ctext x=\"190\" y=\"168\" text-anchor=\"middle\" font-size=\"12\" fill=\"#3730a3\">host_user — uid / gid\u003C/text>\n \u003Ctext x=\"190\" y=\"188\" text-anchor=\"middle\" font-size=\"12\" fill=\"#3730a3\">host_net — TCP sockets\u003C/text>\n \u003Ctext x=\"190\" y=\"208\" text-anchor=\"middle\" font-size=\"12\" fill=\"#3730a3\">host_sleep_ms — blocking sleep\u003C/text>\n\n {/* Layer 2 */}\n \u003Crect x=\"360\" y=\"100\" width=\"300\" height=\"120\" rx=\"8\" fill=\"#ecfdf5\" stroke=\"#a7f3d0\" />\n \u003Ctext x=\"510\" y=\"124\" text-anchor=\"middle\" font-size=\"14\" font-weight=\"600\" fill=\"#065f46\">Layer 2: kernel-backed WASI shim\u003C/text>\n \u003Ctext x=\"510\" y=\"148\" text-anchor=\"middle\" font-size=\"12\" fill=\"#065f46\">stdio through the kernel bridge\u003C/text>\n \u003Ctext x=\"510\" y=\"168\" text-anchor=\"middle\" font-size=\"12\" fill=\"#065f46\">mounts mirrored as preopens\u003C/text>\n \u003Ctext x=\"510\" y=\"188\" text-anchor=\"middle\" font-size=\"12\" fill=\"#065f46\">read-only tiers enforced\u003C/text>\n \u003Ctext x=\"510\" y=\"208\" text-anchor=\"middle\" font-size=\"12\" fill=\"#065f46\">paths confined to their mount\u003C/text>\n\n {/* Arrows down to kernel */}\n \u003Cline x1=\"190\" y1=\"220\" x2=\"190\" y2=\"280\" stroke=\"#71717a\" stroke-width=\"2\" marker-end=\"url(#arrow)\" />\n \u003Cline x1=\"510\" y1=\"220\" x2=\"510\" y2=\"280\" stroke=\"#71717a\" stroke-width=\"2\" marker-end=\"url(#arrow)\" />\n\n {/* Kernel */}\n \u003Crect x=\"40\" y=\"284\" width=\"620\" height=\"56\" rx=\"8\" fill=\"#fafafa\" stroke=\"#d4d4d8\" />\n \u003Ctext x=\"350\" y=\"308\" text-anchor=\"middle\" font-size=\"15\" font-weight=\"600\" fill=\"#18181b\">Kernel: virtual filesystem, process table, socket table\u003C/text>\n \u003Ctext x=\"350\" y=\"328\" text-anchor=\"middle\" font-size=\"12\" fill=\"#52525b\">same paths that back JavaScript guests — no host escape\u003C/text>\n\n \u003Cdefs>\n \u003Cmarker id=\"arrow\" markerWidth=\"10\" markerHeight=\"10\" refX=\"6\" refY=\"3\" orient=\"auto\" markerUnits=\"strokeWidth\">\n \u003Cpath d=\"M0,0 L6,3 L0,6 Z\" fill=\"#71717a\" />\n \u003C/marker>\n \u003C/defs>\n\u003C/svg>\n\n## Layer 1: custom host import modules\n\nStandard WASI cannot express `fork` / `exec`, `getuid`, or `connect`. agentOS\ndeclares extra WebAssembly import modules that the host runtime implements, so\nguest libc can call them as if they were ordinary syscalls. These bindings live\nin the `wasi-ext` crate and cover three areas:\n\n- **`host_process`**: process management. Spawn a child process (argv, env, inherited stdio fds, working directory), wait for a child to exit, and related file-descriptor operations. This is what gives a WASM `sh` real [child process](/docs/processes) semantics; spawns go through the kernel process table.\n- **`host_user`**: user and group identity (uid, gid, user info). Base WASI has no concept of a user; this lets tools that call `getuid` / `getgid` see the VM's virtualized identity.\n- **`host_net`**: TCP sockets (connect, listen, send, receive) through the kernel socket table, gated by the same [network permission policy](/docs/networking) as everything else. Base WASI has no general socket API.\n\nA small `host_sleep_ms` binding provides blocking sleep. Together these let a\nguest compiled for `wasip1` behave as if it had a process model, user identity,\nand a network, all virtualized.\n\n```c\n// Imported from the host runtime, declared by the wasi-ext bindings.\n// Guest libc calls these as if they were ordinary syscalls.\n__attribute__((import_module(\"host_process\"), import_name(\"proc_spawn\")))\nint host_proc_spawn(const char *argv, const char *envp, int cwd_fd);\n\n// getuid returns an errno; the uid is written through the out-pointer.\n__attribute__((import_module(\"host_user\"), import_name(\"getuid\")))\nint host_getuid(unsigned int *ret_uid);\n\n__attribute__((import_module(\"host_net\"), import_name(\"net_connect\")))\nint host_net_connect(int fd, const char *addr, int addr_len);\n```\n\n## Layer 2: the kernel-backed WASI shim\n\nThe second layer adapts the standard WASI calls themselves so that programs\nbuilt against a normal libc behave correctly inside the VM. The embedded shim:\n\n- **Routes stdio through the kernel.** `fd_read` / `fd_write` on the standard descriptors go through the kernel stdio bridge rather than host file descriptors, so output stays inside the VM and honors PTYs and redirection.\n- **Fills in libc expectations.** For example `fcntl(F_SETFL)` is serviced via `fd_fdstat_set_flags`, so flag changes that libc performs do not fail.\n- **Mirrors mounts as preopens.** The preopen table reflects the VM's guest path mappings, so mounted directories are visible to WASM path resolution exactly as they are to JS and to `node:fs`.\n- **Enforces read-only tiers.** `path_open` rejects create / truncate / write flags on read-only mounts while still allowing non-mutating opens (directory traversal, `O_DIRECTORY`), so read-only mounts stay read-only without breaking `find`, `ls`, and friends.\n- **Confines paths to their mount.** Targets are resolved beneath the specific preopen's root, so `..` segments cannot escape one mount into a sibling mount or a host path.\n\n```\nfd_read(0) -> kernel stdio bridge (not a host fd)\nfcntl(fd, F_SETFL) -> fd_fdstat_set_flags (libc flag changes succeed)\npath_open(\"/data/x\") -> resolved under the /data preopen root\npath_open(..O_CREAT) -> rejected on a read-only mount\npath_open(\"../../etc\")-> stays inside the mount; cannot escape\n```","src/content/docs/docs/architecture/posix-syscalls.mdx","afdc817a0338ca3a","docs/architecture/processes",{"id":386,"data":388,"body":391,"filePath":392,"digest":393,"deferredRender":16},{"title":389,"description":390,"skill":16},"Processes","Internals of the kernel process model: the virtual process table, how spawns are serviced, stdio bridging, PTYs, and how WASM sh and coreutils map onto it.","This page is an internals deep-dive on the kernel's **process model**: the data\nstructures and syscall paths behind every guest process. For the client-facing\nAPI (`exec`, `spawn`, `openShell`, lifecycle, the process tree), see\n[Processes & Shell](/docs/processes). For the surrounding component and trust\nmodel, see [Architecture](/docs/architecture).\n\nTwo invariants frame everything below:\n\n- **No real host process is ever spawned for guest work.** Every guest process is an entry in a kernel-owned virtual process table, not an OS process. Guest JavaScript runs in V8 isolates; guest commands like `sh` and coreutils run as WebAssembly. Neither is `node` or a host binary.\n- **Every process operation is a syscall into the kernel.** Spawning, waiting, signaling, reading stdout, and resizing a PTY all cross from the untrusted executor into the sidecar-owned kernel, which services them against virtualized resources.\n\n## The virtual process table\n\nEach VM owns one process table. It is the authority for what is \"running\"\ninside that VM; nothing in it corresponds to a host PID.\n\n- **Per-VM and isolated.** Two VMs have two independent tables. A PID in one VM is meaningless in another, and processes are never visible across the VM boundary.\n- **Holds every guest process,** not only the ones a client started explicitly. A `spawn` from the client, a child spawned by guest `node:child_process`, and the processes behind a shell pipeline are all table entries. This is why the system-wide views (`allProcesses`, `processTree`) can show more than what the client launched.\n- **Tracks lifecycle and lineage.** Each entry carries its PID, the command and arguments, parent PID (so the tree can be reconstructed), running/exited status, exit code once collected, and its attached stdio endpoints.\n- **Records a driver.** An entry knows which execution backend services it (for example a V8 isolate versus a WASM runtime). This is the `driver` field surfaced on `allProcesses`. Drivers differ in *how* the code runs; they share the same table, the same kernel-owned stdio, and the same boundary.\n\n\u003CNote>The process table is part of the kernel the sidecar owns. The executor never mutates it directly; it can only ask the kernel to create, wait on, or signal an entry. That request-only relationship is the sidecar-to-executor boundary applied to processes.\u003C/Note>\n\n## How a spawn is serviced\n\nA spawn, whether it originates from a client `spawn`/`exec` call or from guest\n`node:child_process`, follows one path through the kernel:\n\n1. **The request crosses into the kernel.** A client call arrives over the wire protocol; a guest call arrives as a syscall from the executor. Either way the kernel, not the caller, performs the work.\n2. **Permission check.** The kernel applies the VM's permission policy before doing anything. Process execution is denied by default and must be granted; the policy is trusted input, the guest making the request is not.\n3. **Resolve the program.** The command is resolved against the VM's virtual filesystem (PATH lookup over the VFS), not the host. The resolved program decides the driver: a JavaScript entrypoint runs in a V8 isolate; a `.wasm` program (including `sh` and coreutils) runs on the WASM runtime.\n4. **Allocate the table entry.** The kernel assigns a virtual PID, records the command, arguments, environment, working directory, and parent PID, and links stdio endpoints (see below).\n5. **Start execution.** The driver begins running the program. For a one-shot `exec` the kernel additionally collects stdout, stderr, and the exit code and returns them as the call's result; for `spawn` it leaves the process running and streams output via events.\n6. **Reap and record exit.** When the program finishes, the kernel records the exit code on the table entry and marks it exited, which is what a `wait`/`waitProcess` resolves against and what `processExit` reports.\n\nSignals (`stopProcess` / SIGTERM, `killProcess` / SIGKILL) are the same shape: a\nrequest into the kernel, which applies it to the virtualized process rather than\nto any host process.\n\n## Stdio bridging\n\nStandard streams are kernel-owned objects, not host file descriptors. Each\nprocess entry has stdin, stdout, and stderr endpoints that the kernel wires up\nwhen the entry is created.\n\n- **Capture vs. stream.** For `exec`, the kernel buffers stdout and stderr and hands them back when the process exits. For `spawn`, output is delivered incrementally as `processOutput` events tagged with the PID and the stream (`stdout`/`stderr`), and `processExit` signals completion.\n- **Writable stdin.** `writeProcessStdin` pushes bytes into the process's stdin endpoint; `closeProcessStdin` closes the write side so programs that read to EOF (like `cat`) can finish. None of this touches a real pipe on the host.\n- **Pipes between processes.** Shell pipelines (`a | b`) connect one process's stdout endpoint to the next process's stdin endpoint through kernel-owned pipes. The pipe is a virtual object in the kernel, so a pipeline behaves like Linux without any host IPC.\n\nBecause these endpoints are kernel objects, the same bridging works identically\nwhether the process is a V8 isolate or a WASM program; the driver writes to and\nreads from kernel stdio, not from anything host-provided.\n\n## PTYs and interactive shells\n\nAn interactive shell needs a terminal, not just piped stdio: line editing, job\ncontrol signals, and window size all depend on a PTY. The kernel provides\nvirtual PTY devices for this.\n\n- **A shell is a process plus a PTY.** `openShell` allocates a kernel PTY and starts a shell process attached to it, returning a `shellId`. The PTY is a virtualized terminal device, never a host `/dev/pts` entry.\n- **Bidirectional terminal I/O.** `writeShell` feeds keystrokes into the PTY master side; everything the shell and its children emit comes back as `shellData` events. This carries terminal control sequences, so full-screen TUIs behave correctly.\n- **Resize is a terminal operation.** `resizeShell` updates the PTY's window size (columns and rows), which the kernel propagates to the foreground process the way a real terminal resize would, so programs relying on `TIOCGWINSZ`-style sizing redraw correctly.\n- **Teardown.** `closeShell` tears down the PTY and the attached shell process. An open shell keeps the VM active, the same way an open PTY keeps a session alive on a real system.\n\n## WASM sh and coreutils on the process model\n\nThe shell and the standard commands behind process execution are not special\nhost helpers; they are ordinary guest processes that happen to be WebAssembly.\nFor the full WASM execution model see [WASM VM](/docs/architecture/posix-syscalls); here is how it\nmaps onto the process table specifically.\n\n- **They are normal table entries.** Running `sh`, `ls`, `cat`, etc. allocates virtual PIDs and table entries exactly like any other process, with the WASM driver recorded on each. A pipeline of coreutils is several entries linked by kernel pipes.\n- **POSIX process semantics are virtualized, not borrowed from the host.** Plain WASI has no process model (no `fork`/`exec`/`wait`). agentOS supplies those semantics through kernel-backed host imports, so a WASM program that spawns and waits on a child drives the *same* kernel process table that JS guests use. A coreutil spawning a subcommand is one table entry creating another.\n- **Same stdio, same PTY.** WASM processes read and write the kernel stdio endpoints described above, and a shell built from WASM `sh` attaches to a kernel PTY just like any interactive shell. The driver differs; the kernel-owned plumbing does not.\n\nThis is why the process model is uniform: whether an entry is a V8 isolate or a\nWASM binary, it lives in the same per-VM table, goes through the same\npermission-checked spawn path, and uses the same kernel-owned stdio and PTYs.\n\n## See also\n\n- [Processes & Shell](/docs/processes): the client API for running and managing processes.\n- [WASM VM](/docs/architecture/posix-syscalls): how WebAssembly guests get POSIX process, user, and network semantics.\n- [Architecture](/docs/architecture): components, the trust boundary, and the request lifecycle.\n- [Permissions](/docs/permissions): the policy the kernel checks on every spawn.","src/content/docs/docs/architecture/processes.mdx","000c86cdd9459fa0","docs/agents/pi",{"id":394,"data":396,"body":399,"filePath":400,"digest":401,"deferredRender":16},{"title":397,"description":398,"skill":16},"Pi","Run the Pi coding agent inside a VM with extensions and custom configuration.","## Quick start\n\n\u003CCodeGroup>\n\u003CCodeSnippet file=\"examples/pi/server.ts\" />\n\n\u003CCodeSnippet file=\"examples/pi/client.ts\" region=\"quick-start\" />\n\u003C/CodeGroup>\n\nRead [Sessions](/docs/sessions) first for session options, streaming events, prompts, and lifecycle management.\n\n## LLM Credentials\n\nSet the relevant variable on the session's `env`, sourced from your server's environment:\n\n- `ANTHROPIC_API_KEY` — Anthropic (Claude), the default.\n- Other providers — use the provider-named key (e.g. `OPENAI_API_KEY`, `GEMINI_API_KEY`, `OPENROUTER_API_KEY`).\n\nSee [LLM Credentials](/docs/llm-credentials), and Pi's [providers docs](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/providers.md) for the full list.\n\n## Skills\n\nPi discovers `SKILL.md` files from its skills directory. Write the skill into the VM before creating a session and Pi loads it automatically.\n\n\u003CCodeSnippet file=\"examples/pi/client.ts\" region=\"skill\" />\n\n## MCP servers\n\nExpose extra tools to the agent by passing `mcpServers` to `createSession`. Both local child-process servers and remote URLs are supported.\n\n\u003CCodeSnippet file=\"examples/pi/client.ts\" region=\"mcp\" />\n\n\u003CNote>\n**Pre-install `npx`-launched servers.** A local server started with `npx -y …` writes install progress to **stdout** on its first run, which corrupts the MCP stdio handshake (you'll see `Connection closed`). Pre-install it in the VM so `npx` is silent — `await agent.exec(\"npm install -g @modelcontextprotocol/server-filesystem\")` before the session — or pin the package and point `command` at the installed binary.\n\u003C/Note>\n\n## Extensions\n\nPi supports [extensions](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent/examples/extensions) that let you register custom tools, modify the system prompt, and hook into agent lifecycle events. Write a `.js` file into the VM's extensions directory before creating a session and Pi discovers it automatically.\n\nPi scans two directories for `.js` extension files:\n\n| Directory | Scope |\n|-----------|-------|\n| `~/.pi/agent/extensions/` | Global — applies to all Pi sessions |\n| `\u003Ccwd>/.pi/extensions/` | Project — applies only when cwd matches |\n\n\u003CCodeSnippet file=\"examples/pi/client.ts\" region=\"extension\" />\n\nSee the [Pi extension documentation](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent/examples/extensions) for the full extension API.\n\n## Customizing the agent\n\nPi is a built-in agent, but it's just a software package under the hood. To ship your own ACP adapter, swap the underlying agent SDK, or register a tweaked Pi build as a new agent, see [Custom Agents](/docs/agents/custom).","src/content/docs/docs/agents/pi.mdx","754e14625aa08f9a","docs/architecture/packages-and-command-resolution",{"id":402,"data":404,"body":407,"filePath":408,"digest":409,"deferredRender":16},{"title":405,"description":406,"skill":16},"Packages & Command Resolution","How software is packaged, linked, resolved, and executed in an agentOS VM: a package is a directory, resolution is a $PATH walk, and a file's header picks its runtime.","How a command name becomes a running program, and how the software that provides it\nis packaged and linked. Everything is real files under\n[`/opt/agentos`](/docs/architecture/filesystem) — there is no command registry; the\nfilesystem and `$PATH` are the only source of truth. For the host API that produces\npackages, see [Software Definition](/docs/custom-software/definition).\n\n## Overview\n\n\u003Csvg viewBox=\"0 0 760 470\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"From a command name to a running program: resolve over PATH, dispatch by header, run under the VM policy\" style=\"width:100%;max-width:680px;height:auto;font-family:system-ui,sans-serif\">\n \u003Cdefs>\n \u003Cmarker id=\"pcr-ah\" viewBox=\"0 0 10 10\" refX=\"8.5\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\">\n \u003Cpath d=\"M0 0 L10 5 L0 10 z\" fill=\"#64748b\"/>\n \u003C/marker>\n \u003C/defs>\n \u003Crect x=\"280\" y=\"16\" width=\"200\" height=\"42\" rx=\"7\" fill=\"#eef2ff\" stroke=\"#6366f1\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"380\" y=\"42\" text-anchor=\"middle\" font-size=\"14\" fill=\"#1e1b4b\">exec \u003Ctspan font-family=\"ui-monospace,monospace\">\"pi\"\u003C/tspan>\u003C/text>\n \u003Cline x1=\"380\" y1=\"58\" x2=\"380\" y2=\"84\" stroke=\"#64748b\" stroke-width=\"1.5\" marker-end=\"url(#pcr-ah)\"/>\n \u003Crect x=\"265\" y=\"86\" width=\"230\" height=\"42\" rx=\"7\" fill=\"#f8fafc\" stroke=\"#94a3b8\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"380\" y=\"112\" text-anchor=\"middle\" font-size=\"13\" fill=\"#0f172a\">\u003Ctspan font-family=\"ui-monospace,monospace\">$PATH\u003C/tspan> walk over the VFS\u003C/text>\n \u003Cline x1=\"380\" y1=\"128\" x2=\"380\" y2=\"154\" stroke=\"#64748b\" stroke-width=\"1.5\" marker-end=\"url(#pcr-ah)\"/>\n \u003Crect x=\"235\" y=\"156\" width=\"290\" height=\"42\" rx=\"7\" fill=\"#f8fafc\" stroke=\"#94a3b8\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"380\" y=\"176\" text-anchor=\"middle\" font-size=\"12.5\" font-family=\"ui-monospace,monospace\" fill=\"#0f172a\">/opt/agentos/bin/pi\u003C/text>\n \u003Ctext x=\"380\" y=\"191\" text-anchor=\"middle\" font-size=\"10.5\" fill=\"#64748b\">a real symlink in the VFS\u003C/text>\n \u003Cline x1=\"380\" y1=\"198\" x2=\"380\" y2=\"224\" stroke=\"#64748b\" stroke-width=\"1.5\" marker-end=\"url(#pcr-ah)\"/>\n \u003Crect x=\"275\" y=\"226\" width=\"210\" height=\"42\" rx=\"7\" fill=\"#fef9c3\" stroke=\"#eab308\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"380\" y=\"252\" text-anchor=\"middle\" font-size=\"13\" fill=\"#422006\">read header (binfmt)\u003C/text>\n \u003Cline x1=\"380\" y1=\"268\" x2=\"102\" y2=\"316\" stroke=\"#64748b\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Cline x1=\"380\" y1=\"268\" x2=\"289\" y2=\"316\" stroke=\"#64748b\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Cline x1=\"380\" y1=\"268\" x2=\"476\" y2=\"316\" stroke=\"#64748b\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Cline x1=\"380\" y1=\"268\" x2=\"660\" y2=\"316\" stroke=\"#ef4444\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Crect x=\"18\" y=\"318\" width=\"168\" height=\"58\" rx=\"7\" fill=\"#ecfeff\" stroke=\"#06b6d4\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"102\" y=\"340\" text-anchor=\"middle\" font-size=\"11\" font-family=\"ui-monospace,monospace\" fill=\"#155e75\">#!…node\u003C/text>\n \u003Ctext x=\"102\" y=\"360\" text-anchor=\"middle\" font-size=\"12.5\" fill=\"#0e2a33\">JavaScript · V8\u003C/text>\n \u003Crect x=\"205\" y=\"318\" width=\"168\" height=\"58\" rx=\"7\" fill=\"#ecfeff\" stroke=\"#06b6d4\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"289\" y=\"340\" text-anchor=\"middle\" font-size=\"11\" font-family=\"ui-monospace,monospace\" fill=\"#155e75\">#!…python3\u003C/text>\n \u003Ctext x=\"289\" y=\"360\" text-anchor=\"middle\" font-size=\"12.5\" fill=\"#0e2a33\">Python · Pyodide\u003C/text>\n \u003Crect x=\"392\" y=\"318\" width=\"168\" height=\"58\" rx=\"7\" fill=\"#ecfeff\" stroke=\"#06b6d4\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"476\" y=\"340\" text-anchor=\"middle\" font-size=\"11\" font-family=\"ui-monospace,monospace\" fill=\"#155e75\">{'\\\\0asm'}\u003C/text>\n \u003Ctext x=\"476\" y=\"360\" text-anchor=\"middle\" font-size=\"12.5\" fill=\"#0e2a33\">WebAssembly\u003C/text>\n \u003Crect x=\"579\" y=\"318\" width=\"163\" height=\"58\" rx=\"7\" fill=\"#fee2e2\" stroke=\"#ef4444\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"660\" y=\"340\" text-anchor=\"middle\" font-size=\"10.5\" font-family=\"ui-monospace,monospace\" fill=\"#7f1d1d\">ELF / Mach-O / PE\u003C/text>\n \u003Ctext x=\"660\" y=\"360\" text-anchor=\"middle\" font-size=\"12.5\" fill=\"#7f1d1d\">ENOEXEC\u003C/text>\n \u003Cline x1=\"102\" y1=\"376\" x2=\"102\" y2=\"404\" stroke=\"#22c55e\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Cline x1=\"289\" y1=\"376\" x2=\"289\" y2=\"404\" stroke=\"#22c55e\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Cline x1=\"476\" y1=\"376\" x2=\"476\" y2=\"404\" stroke=\"#22c55e\" stroke-width=\"1.3\" marker-end=\"url(#pcr-ah)\"/>\n \u003Crect x=\"18\" y=\"406\" width=\"542\" height=\"40\" rx=\"7\" fill=\"#f0fdf4\" stroke=\"#22c55e\" stroke-width=\"1.5\"/>\n \u003Ctext x=\"289\" y=\"431\" text-anchor=\"middle\" font-size=\"12.5\" fill=\"#14532d\">spawn under the VM permission policy\u003C/text>\n\u003C/svg>\n\n- **Resolve** — a real `$PATH` walk over the VFS; the first executable match wins.\n- **Dispatch** — by the file's *header* (`binfmt`): a `#!` shebang or a magic number. Never the name, never the extension.\n- **Run** — on one of three runtimes: JavaScript (V8), WebAssembly, Python (Pyodide). See [Processes](/docs/architecture/processes).\n- **Confine** — every process runs under the VM's single [permission policy](/docs/security-model). No per-command tiers.\n\n## Packages\n\nA package is a directory; its metadata is a normal `package.json` (`name`, `version`,\nand a `bin` command map) plus a small `agentos-package.json` (the agentOS-specific\n`name`/`agent`/`provides`). The shipped package contains **real files** — it's a plain npm\ndependency. The `/opt/agentos/\u003Cname>/\u003Cversion>/` tree below, with its `bin/` symlink farm,\nis what the runtime **projects** from that package when it mounts it:\n\n```\n/opt/agentos/\u003Cname>/\u003Cversion>/\n├── package.json # name, version, and the \"bin\" map (command → entry file)\n├── agentos-package.json # agentOS metadata: name, optional agent block, provides\n├── bin/ # symlinks the PROJECTION builds from package.json \"bin\"\n│ ├── ls → ../libexec/coreutils # → multicall blob\n│ └── vdir → ../libexec/coreutils # an \"alias\" is just another symlink\n├── libexec/coreutils # helpers run by other programs, never on $PATH\n├── node_modules/ | lib/ # support payload (a JS CLI's flat, self-contained closure)\n└── share/man/man1/ls.1 # man pages and other FHS content\n/opt/agentos/\u003Cname>/current → \u003Cversion> # version pointer; upgrade re-points it (atomic rename)\n```\n\n| Path | Contents |\n|---|---|\n| `package.json` | `name`, `version`, and a `bin` map (command → entry file). |\n| `agentos-package.json` | agentOS metadata the sidecar reads on mount: `name`, an optional `agent` block, and any `provides` (files/env). Generated for command/WASM packages; carries the `agent` block for agents. |\n| `bin/` | Command symlinks the projection builds from `package.json` `bin`; each basename is the command name. (Not part of the shipped package — npm can't carry symlinks.) |\n| `libexec/` | Helpers invoked by other programs, never on `$PATH` (e.g. a multicall blob). |\n| `node_modules/`, `lib/` | Non-executable payload — bundled deps and assets. |\n| `share/` | FHS data — `share/man/man\u003Cn>/*`, etc. |\n| `current` | Symlink `→ \u003Cversion>`; switching versions is one atomic rename. |\n\n```jsonc\n// package.json — commands come from \"bin\"; an agent's ACP entrypoint is just one of them\n{ \"name\": \"pi\", \"version\": \"0.60.0\", \"bin\": { \"pi-acp\": \"dist/acp.js\" } }\n```\n\nA directory is a **valid package** when:\n\n- **Commands come from `package.json` `bin`** (command → a real entry file), and each entry\n **dispatches by header** — a magic number or `#!` shebang, no `.wasm`/`.js` extension or\n `runtime`/`type` field; a headerless entry is `ENOEXEC`. The package ships **no symlinks**\n (npm-safe); the runtime builds the `bin/` farm under `/opt/agentos` itself.\n- **Aliases are symlinks** in the projected `bin/` farm — several names for one program (or a\n multicall blob); `argv[0]` is the invoked name.\n- **It is self-contained** — every import/require/asset resolves inside the package; nothing\n comes from a host `node_modules`, pnpm store, or workspace at runtime\n ([packaging](/docs/custom-software/definition) flattens/bundles deps in).\n- **Minimal metadata** — `package.json` carries only the command set (`bin`) and `version`; there\n is no command list beyond `bin`, no permission tiers (the [VM policy](#confinement--trust)\n governs every command), and no dependency list. A small **`agentos-package.json`** alongside it\n holds the agentOS-specific fields the sidecar reads when it mounts the package — the `name`, an\n optional `agent` block, and any `provides` (files/env). The client never carries this on the\n wire; it forwards only the package directory.\n\n## Linking\n\nLinking is creating the `bin/` symlinks in a `$PATH` directory. agentOS follows Homebrew:\n`/opt/agentos/\u003Cname>` is the cellar, and every command is symlinked into one managed prefix,\n**`/opt/agentos/bin`**, which is on `$PATH`. The standard dirs (`/usr/bin`, `/usr/local/bin`,\n`/bin`) stay ordinary writable Linux dirs — agentOS never writes to them.\n\n\u003Csvg viewBox=\"0 0 760 150\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"PATH search order, left to right, first match wins\" style=\"width:100%;max-width:720px;height:auto;font-family:system-ui,sans-serif\">\n \u003Cdefs>\n \u003Cmarker id=\"pcr-ah2\" viewBox=\"0 0 10 10\" refX=\"8.5\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto\">\n \u003Cpath d=\"M0 0 L10 5 L0 10 z\" fill=\"#94a3b8\"/>\n \u003C/marker>\n \u003C/defs>\n \u003Ctext x=\"16\" y=\"20\" font-size=\"12\" fill=\"#0f172a\">Searched left → right — first match wins (left shadows right)\u003C/text>\n \u003Cline x1=\"16\" y1=\"33\" x2=\"744\" y2=\"33\" stroke=\"#cbd5e1\" stroke-width=\"1.2\" marker-end=\"url(#pcr-ah2)\"/>\n \u003Crect x=\"16\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" stroke-width=\"1.3\"/>\n \u003Ctext x=\"65.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#334155\">/usr/local/sbin\u003C/text>\n \u003Crect x=\"121\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" stroke-width=\"1.3\"/>\n \u003Ctext x=\"170.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#334155\">/usr/local/bin\u003C/text>\n \u003Crect x=\"226\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#eef2ff\" stroke=\"#6366f1\" stroke-width=\"2\"/>\n \u003Ctext x=\"275.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#3730a3\">/opt/agentos/bin\u003C/text>\n \u003Crect x=\"331\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" stroke-width=\"1.3\"/>\n \u003Ctext x=\"380.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#334155\">/usr/sbin\u003C/text>\n \u003Crect x=\"436\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" stroke-width=\"1.3\"/>\n \u003Ctext x=\"485.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#334155\">/usr/bin\u003C/text>\n \u003Crect x=\"541\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" stroke-width=\"1.3\"/>\n \u003Ctext x=\"590.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#334155\">/sbin\u003C/text>\n \u003Crect x=\"646\" y=\"50\" width=\"99\" height=\"44\" rx=\"6\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" stroke-width=\"1.3\"/>\n \u003Ctext x=\"695.5\" y=\"76\" text-anchor=\"middle\" font-size=\"9\" font-family=\"ui-monospace,monospace\" fill=\"#334155\">/bin\u003C/text>\n \u003Ctext x=\"16\" y=\"130\" font-size=\"11\" fill=\"#475569\">agentOS links into \u003Ctspan font-family=\"ui-monospace,monospace\" fill=\"#4338ca\">/opt/agentos/bin\u003C/tspan>; the rest are ordinary writable Linux dirs — drop a binary in \u003Ctspan font-family=\"ui-monospace,monospace\" fill=\"#4338ca\">/usr/local/bin\u003C/tspan> to shadow an agentOS tool.\u003C/text>\n\u003C/svg>\n\n| Software | Stored | Linked into |\n|---|---|---|\n| Base, mounted, and runtime-installed agentOS software | `/opt/agentos/\u003Cpkg>/\u003Cver>` (or the mount) | `/opt/agentos/bin` |\n| The user's own files | wherever they put them | `/usr/local/bin`, `/usr/bin`, … (normal) |\n\n- **Base & mounts** link into `/opt/agentos/bin` in a **read-only layer** projected from the\n host and shared across VMs — the symlinks are real but cost nothing per boot. A mounted host\n directory is linked the same way, with no copy.\n- **Runtime installs** add symlinks to `/opt/agentos/bin` in the **writable layer** via\n [`agentos-software link`](#the-agentos-software-cli) — ordinary symlinks, found by the normal walk.\n\n## Persistence\n\nLinks and installed files are **filesystem entries**, so they persist exactly when their\n[filesystem](/docs/architecture/filesystem) layer does — the same rule as VFS-persistent\n`pip`. A snapshotted/persistent volume keeps runtime installs and links across restart; an\nephemeral one drops them on teardown. There is no package-specific persistence mechanism.\n\n\u003CWarning>\nPersisting a layer an untrusted guest can write to also persists whatever the guest linked\nthere. Treat a guest-writable `/usr/local/bin` as guest-controlled on restore (see\n[Confinement & trust](#confinement--trust)).\n\u003C/Warning>\n\n## Execution dispatch (binfmt)\n\nA resolved file's leading bytes are read into a fixed buffer and dispatched like the Linux\nkernel's binary-format handlers. The command's **name plays no part** — `python3`, `node`,\nand `pi` are runtimes only by virtue of their files' headers.\n\n| Header | Result |\n|---|---|\n| `#!` at bytes 0–1 (`binfmt_script`) | the interpreter named on the line |\n| `\\0asm` (`00 61 73 6d`) | WebAssembly runtime |\n| `\\x7fELF` / Mach-O / PE | **`ENOEXEC`** — foreign binary format, no native-arch handler |\n| anything else | `ENOEXEC` (no implicit `/bin/sh` fallback here) |\n\nShebang handling matches `binfmt_script`:\n\n- The interpreter path is **literal and absolute** — not `$PATH`-searched. `#!/usr/bin/env node`\n works only because `/usr/bin/env` looks up its argument.\n- At most **one** argument follows, **not** whitespace-split (`#!/usr/bin/env node --flag` passes\n `node --flag` as a single arg).\n- The header read is bounded to a fixed buffer (`BINPRM_BUF_SIZE`); a longer line truncates.\n Interpreter chaining is depth-bounded (`ELOOP`); a missing interpreter is **`ENOENT`**, not `ENOEXEC`.\n\n\u003CNote>\n**Shell fallback.** On `ENOEXEC`, a POSIX shell re-runs a headerless script via `/bin/sh`. That\nretry lives in the shell ([agentos-shell](/docs/architecture/processes)), not the dispatcher,\nwhich stays strictly `binfmt`-faithful.\n\u003C/Note>\n\n### Multicall (busybox-style)\n\n`bin/ls → ../libexec/coreutils` resolves at open to the shared `coreutils` blob. `argv[0]` is\nthe caller's value **verbatim** (`\"ls\"`) — never derived from the symlink — and the blob selects\nits applet with `basename(argv[0])`, like busybox. Always invoke via the `bin/` name; calling the\nblob by its own path yields an `argv[0]` that selects no applet.\n\n## Command resolution\n\nA `$PATH` walk over the [VFS](/docs/architecture/filesystem), full Linux semantics:\n\n- A name **containing `/`** bypasses `$PATH` and resolves directly (relative to cwd, or absolute).\n- Otherwise each `:`-separated dir is searched in order; the first **executable** regular file\n wins (execute bit required — a non-executable match yields `EACCES`). Left shadows right.\n- An **empty `$PATH` element** (leading/trailing/`::`) means the **current working directory** —\n the POSIX footgun, kept for fidelity.\n- Matches are real VFS files/symlinks — `ls -l`-able, `stat`-able, removable, replaceable. The\n filesystem is authoritative; there is no resolution cache to grow stale.\n\n## The `agentos-software` CLI\n\n```\nagentos-software link \u003Cpath>\n```\n\n- `\u003Cpath>` is a package directory or a node module directory (its `package.json` `bin` map is\n the command list).\n- It brokers a request to the sidecar, which owns the filesystem; the CLI has no privilege of\n its own.\n- Linked names are validated (no `/`, `..`, control chars, overlong names), and for a\n guest-supplied package each symlink target must resolve inside the package root.\n\n## Confinement & trust\n\nEvery process runs under the VM's single [permission policy](/docs/security-model) — like a\nLinux process running with its user/namespace/container privileges, not privileges declared by\nthe binary. A package cannot grant itself permissions. The [trust boundary](/docs/security-model)\nis the sidecar (trusted) vs. the guest (untrusted):\n\n- **Linking changes discoverability, not privilege** — the policy is enforced at spawn,\n regardless of how a command was found.\n- **Shadowing is allowed, Linux-style** — a guest may drop a `node`/`ls` into a writable `$PATH`\n dir; trusted in-VM components defend by invoking tools via **absolute paths** (or a `$PATH`\n that excludes guest-writable dirs). The shadowing binary still runs only under the VM policy.\n- **Guest env is sanitized** like a privileged exec — `LD_*`, `DYLD_*`, `NODE_OPTIONS`, `PATH`,\n `BASH_ENV`, `*PRELOAD` are stripped, as glibc does under `AT_SECURE`.\n- **Trusted vs. guest packages** — symlink-escape checks apply only to guest-writable runtime packages.\n- **Bounded** — the runtime link count is bounded; it warns on approach and fails with a typed\n error naming the limit (see [Limits & Observability](/docs/architecture/limits-and-observability)).\n\n## See also\n\n- [Software Definition](/docs/custom-software/definition) — the host API that produces these packages.\n- [Processes](/docs/architecture/processes) — the JavaScript, WebAssembly, and Python runtimes.\n- [Filesystem](/docs/architecture/filesystem) — the VFS, layers, and persistence.\n- [Security Model](/docs/security-model) — the trust boundary and VM permission policy.","src/content/docs/docs/architecture/packages-and-command-resolution.mdx","de3879554bd53da6","docs/custom-software/building-wasm",{"id":410,"data":412,"body":415,"filePath":416,"digest":417,"deferredRender":16},{"title":413,"description":414},"Building Binaries","Compile WASM command binaries for agentOS from source in the secure-exec registry.","WASM command packages ship **compiled `.wasm` binaries** in their `bin/` that run inside the VM as guest commands. The binaries are build artifacts and are not checked into git, so to add or change a command you build it from source in the **secure-exec registry**.\n\n\u003CNote>\nYou only need this to author new commands. To use existing ones, install the published package (e.g. `@agentos-software/ripgrep`) and pass it to `software`. See [using the registry](#using-the-registry) below.\n\u003C/Note>\n\n## Where it lives\n\nCommand source and packages live under `registry/` in [secure-exec](https://github.com/rivet-dev/secure-exec/tree/main/registry):\n\n- **`registry/native/crates/commands/\u003Cname>/`**: the Rust source for each command — a cargo package named `cmd-\u003Cname>` that emits a `\u003Cname>` binary.\n- **`registry/native/c/`**: the C source for the C-built commands.\n- **`registry/software/\u003Cname>/`**: the npm package for each command set (`@agentos-software/\u003Cname>`). It exports a `{ packagePath }` descriptor pointing at the packed `dist/package.aospkg`, and declares which binaries it ships in its `agentos-package.json` (`commands`, plus optional `aliases` and `stubs`).\n\n## Build\n\nEverything runs through `just` recipes at the secure-exec repo root:\n\n```bash\njust registry-native # compile ALL native wasm binaries (slow; once per checkout)\njust registry-native-cmd sh # recompile ONE command (cargo package cmd-sh)\njust registry-build # stage + assemble every registry package\njust registry-build ripgrep # ... or just one\njust registry-status # per-package state; --remote adds npm dist-tags\n```\n\nThe native build compiles each command for `wasm32-wasip1` with the pinned **nightly** toolchain from `rust-toolchain.toml` (the build vendors and patches `std` for WASI), optimizes with `wasm-opt`, and drops the binaries in `registry/native/target/wasm32-wasip1/release/commands/`. C-based commands (e.g. `sqlite3`, `unzip`, `wget`, `zip`) compile with a **wasi-sdk** clang toolchain via `make -C registry/native/c`.\n\nEach package's build then runs the **agentos-toolchain** lifecycle: `agentos-toolchain stage` copies the binaries listed in the package's `agentos-package.json` into its `bin/`, and `agentos-toolchain build` assembles the clean `dist/package/` dir with a `bin` map in its `package.json` and packs it into `dist/package.aospkg` (the `{ packagePath }` target).\n\n## Add a new command package\n\n1. Add the command source as `registry/native/crates/commands/\u003Cname>/` (cargo package `cmd-\u003Cname>`; Rust) or under `registry/native/c/` (C).\n2. Create `registry/software/\u003Cname>/` as an `@agentos-software/\u003Cname>` npm package that exports a `{ packagePath }` descriptor pointing at `dist/package.aospkg`.\n3. Declare the shipped binaries in its `agentos-package.json`: `{ \"commands\": [\"\u003Cname>\"] }` (plus `aliases`/`stubs` if needed).\n4. If it belongs in a meta-package (e.g. `common` or `build-essential`), add it there.\n5. Verify with `just registry-native-cmd \u003Cname> && just registry-build \u003Cname>` and `just registry-test`.\n\n## Let an agent build it\n\nThis is a mechanical, well-scoped task, so you can hand it to a coding agent. A prompt like:\n\n```text\nAdd a WASM command package for `\u003Ccommand>` to the secure-exec registry:\n- put the Rust source at registry/native/crates/commands/\u003Ccommand>/ as a cargo\n package named cmd-\u003Ccommand>,\n- create registry/software/\u003Ccommand>/ as an @agentos-software/\u003Ccommand> npm\n package that exports a { packagePath } descriptor and declares the command in\n its agentos-package.json,\nthen run `just registry-native-cmd \u003Ccommand> && just registry-build \u003Ccommand>`\nand `just registry-test`, and fix any failures.\n```\n\n## Using the registry\n\nInstall a published package and pass it to `software`. Registry WASM packages are `{ packagePath }` descriptors — import and pass them directly:\n\n\u003CCodeSnippet file=\"examples/software/registry-usage.ts\" />\n\nMeta-packages bundle a full set, e.g. `@agentos-software/common` (coreutils, sed, grep, gawk, findutils, diffutils, tar, gzip). Run the commands from the client; see [Processes & Shell](/docs/processes). Browse the full catalog on the [Registry](/registry), and see the package descriptor in [Software Definition](/docs/custom-software/definition). To ship your package to npm or use a local build, see [Publishing Packages](/docs/custom-software/publishing).","src/content/docs/docs/custom-software/building-wasm.mdx","bfc7f01dc0c2ef30","docs/architecture/sessions-persistence",{"id":418,"data":420,"body":423,"filePath":424,"digest":425,"deferredRender":16},{"title":421,"description":422},"Sessions & Persistence","How agentOS, ACP, RivetKit actors, and durable session persistence fit together.","agentOS runs coding agents inside VMs and talks to them through the Agent\nCommunication Protocol (ACP). RivetKit wraps those VMs in durable actors, so a\nsession can survive actor sleep/wake even though the live VM and agent process\ndo not.\n\n## Layers\n\nagentOS session architecture has four layers:\n\n| Layer | Responsibility |\n| --- | --- |\n| RivetKit actor | Owns the public API and durable actor-local SQLite state. |\n| agentOS client | Thin facade used by the actor to create sessions, prompt agents, and call the sidecar. |\n| agentOS sidecar ACP extension | Launches ACP adapters inside the VM, speaks JSON-RPC, handles permissions, and owns resume orchestration. |\n| ACP adapter / agent | Runs inside the VM and speaks ACP over stdio. |\n\nThe actor is durable. The VM is disposable. The ACP agent process is live state\ninside the VM.\n\n## API Shape\n\nThe actor-facing session API is:\n\n- `createSession(agentType, options)`\n- `sendPrompt(sessionId, text)`\n- `closeSession(sessionId)`\n- `listPersistedSessions()`\n- `getSessionEvents(sessionId)`\n\n`sessionId` is the stable, client-facing id. If fallback resume creates a new\nlive ACP session id after wake, the actor keeps an internal\n`externalSessionId -> liveSessionId` remap. Clients keep using the original\n`sessionId`.\n\n## Create Flow\n\n1. The actor calls agentOS `createSession`.\n2. The sidecar starts the ACP adapter process inside the VM.\n3. The sidecar sends ACP `initialize`.\n4. The sidecar sends ACP `session/new`.\n5. The actor persists session metadata in `agent_os_sessions`.\n6. The actor starts capturing ACP `session/update` events for the session.\n\nPersisted session metadata includes:\n\n- `session_id`\n- `agent_type`\n- agent capabilities and agent info\n- create-time `cwd`\n- create-time `env`\n\nThe create-time `cwd` and `env` are used later so resumed sessions start with\nthe same working directory and environment they were created with.\n\n## Prompt Flow\n\n1. The actor receives `sendPrompt(sessionId, text)`.\n2. If the session is persisted but not live in the current VM, the actor lazily\n resumes it first.\n3. The actor writes a synthetic `user_prompt` event before forwarding the\n prompt.\n4. The actor forwards the prompt to the live ACP session id.\n5. The sidecar sends ACP `session/prompt`.\n6. Inbound ACP `session/update` events are captured into\n `agent_os_session_events`.\n\n`agent_os_session_events` is ordered per session. Sequence numbers are allocated\ninside the SQLite insert so concurrent prompt and stream captures cannot reuse\nthe same sequence number.\n\n## Sleep And Wake\n\nWhen a RivetKit actor sleeps:\n\n- the VM is destroyed\n- ACP adapter processes exit\n- the actor's in-memory `live_sessions` remap is lost\n- actor SQLite survives\n\nWhen the actor wakes:\n\n- a fresh VM boots\n- stable session ids still exist in `agent_os_sessions`\n- no ACP session is live yet\n- resume happens lazily on the next prompt\n\n## Resume Flow\n\nOn the first post-wake prompt for a persisted session:\n\n1. The actor reads `agent_os_sessions`.\n2. The actor reconstructs a Markdown transcript from\n `agent_os_session_events`.\n3. The actor writes the transcript to\n `/root/.agentos/threads/\u003CsessionId>.md`.\n4. The actor calls sidecar `resumeSession` with:\n - stable external `sessionId`\n - agent type\n - transcript path\n - persisted create-time `cwd`\n - persisted create-time `env`\n\nThe sidecar then chooses one of two resume paths.\n\n### Native Resume\n\nIf the ACP agent advertises `loadSession` or `resume`, the sidecar sends\n`session/load` or `session/resume`.\n\nWhen native resume succeeds:\n\n- the live ACP id is the stable external `sessionId`\n- the agent restores its own context\n- no transcript preamble is injected\n\nOpenCode uses this path when its own session store is still available in the\ndurable VM filesystem.\n\n### Transcript Fallback\n\nIf native resume is unsupported, or if native resume reports a normalized\n`unknown_session`, the sidecar falls back to a fresh session:\n\n1. The sidecar sends ACP `session/new`.\n2. The sidecar returns the new live ACP id to the actor.\n3. The actor stores `externalSessionId -> liveSessionId`.\n4. The sidecar prepends a one-shot preamble to the next prompt pointing at the\n transcript path.\n\nThe fallback is universal because it only requires the agent to read a file with\nits normal tools. It is lower fidelity than native resume because the transcript\nis pointed to, not automatically loaded into the agent's context window.\n\n## Unknown Session Normalization\n\nAdapters report missing sessions differently. The sidecar normalizes known\nmissing-session shapes into:\n\n```json\n{ \"error\": { \"data\": { \"kind\": \"unknown_session\" } } }\n```\n\nFor example, OpenCode currently reports a missing native session as:\n\n```json\n{ \"code\": -32603, \"data\": { \"details\": \"NotFoundError\" } }\n```\n\nThat shape is captured before normalization in tests, then normalized so the\nresume state machine can safely choose transcript fallback. Other internal\nerrors still propagate as failures.\n\n## Persistence\n\nDurable session state lives in actor SQLite:\n\n| Table | Purpose |\n| --- | --- |\n| `agent_os_sessions` | Stable session registry, agent type, capabilities, agent info, create-time `cwd`, and create-time `env`. |\n| `agent_os_session_events` | Append-only prompt and ACP event log keyed by the stable external `sessionId`. |\n\nThe transcript file is not canonical state. It is a disposable render of\n`agent_os_session_events`, rebuilt on demand during fallback resume.\n\n## What Is Durable\n\n| Data | Survives sleep/wake? | Notes |\n| --- | --- | --- |\n| Actor SQLite | Yes | Stores session registry, events, preview tokens, and other actor data. |\n| VM filesystem | Yes, when backed by the actor sqlite_vfs root | Used by agents and resume transcripts. |\n| Live ACP process | No | Recreated on wake. |\n| Actor in-memory vars | No | Includes the live ACP id remap. |\n| Client-facing `sessionId` | Yes | Stored in `agent_os_sessions`. |\n\n## Where To Look In Code\n\n- Sidecar ACP orchestration:\n `crates/agentos-sidecar/src/acp_extension.rs`\n- agentOS TypeScript client surface:\n `packages/core/src/agent-os.ts`\n- RivetKit actor session actions:\n `rivetkit-rust/packages/rivetkit-agent-os/src/actions/session.rs`\n- RivetKit persistence helpers:\n `rivetkit-rust/packages/rivetkit-agent-os/src/persistence.rs`","src/content/docs/docs/architecture/sessions-persistence.mdx","7a4569a76c301276","docs/custom-software/definition",{"id":426,"data":428,"body":431,"filePath":432,"digest":433,"deferredRender":16},{"title":429,"description":430},"Software Definition","The software-package definition for custom commands and agents in an agentOS VM: a package is a packed .aospkg (or a package directory), declared with defineSoftware({ packagePath }).","**Software** is anything you install into a VM — **commands** (executables in a package's `bin/`) or an **agent** (a package that also exposes an ACP session).\n\nA package is **self-contained**: package it first, then point `defineSoftware()` at it with `{ packagePath }` — the packed `.aospkg` the toolchain emits, or (for local development) the package directory itself. The package's name, optional agent block, and any files/env it provides are authored in an `agentos-package.json` next to your sources; the toolchain compiles that JSON into the `.aospkg`'s embedded manifest at pack time (the JSON itself is never shipped into the VM). Pick the quickstart that matches what you're packaging.\n\n## Quickstart\n\n### WebAssembly\n\n\u003CSteps>\n\n1. **You have** C or Rust source for a command. (Most common commands already ship as `@agentos-software/*` packages you can use directly — compile only new or custom ones.)\n\n2. **Compile it** to WebAssembly — see [Building Binaries](/docs/custom-software/building-wasm). There's **no `pack` step**: WASM binaries are self-contained, so the compile output is already the package — a `bin/` of `\\0asm` files plus a `package.json` for the name/version:\n\n ```\n my-cmds/\n ├── package.json\n └── bin/\n ├── tool-a # \\0asm WebAssembly\n └── tool-b\n ```\n\n3. **Define it** — point `defineSoftware()` at that directory:\n\n \u003CCodeSnippet file=\"examples/software/quickstart-wasm/my-cmds.ts\" />\n\n4. **Use it** — pass it to a VM; the commands are on `$PATH`:\n\n \u003CCodeSnippet file=\"examples/software/quickstart-wasm/index.ts\" />\n\n\u003C/Steps>\n\n### Node.js\n\n\u003CSteps>\n\n1. **You have** a local project whose `package.json` `bin` names its commands:\n\n ```\n my-tool/\n ├── package.json # \"bin\": { \"my-tool\": \"cli.js\" }\n └── cli.js # #!/usr/bin/env node\n ```\n\n2. **Package it** — `pack` installs the full dependency closure into a self-contained package directory (a flat `node_modules` plus a `bin` map of real files):\n\n ```bash\n npx @rivet-dev/agentos-toolchain pack ./my-tool\n # writes ./my-tool-package/ (override the location with --out \u003Cdir>)\n # my-tool-package/\n # ├── package.json # \"bin\": { \"my-tool\": \"node_modules/my-tool/cli.js\" }\n # └── node_modules/ # flat, self-contained closure\n ```\n\n Commands come from the package's `package.json` `bin` map — **real files, no symlinks** — so the result ships cleanly as an npm dependency. (The runtime makes the `/opt/agentos/bin` symlinks itself when it mounts the package.) A native `.node` addon is an error (it can't run in V8); re-run with `--prune-native` to drop unreachable ones.\n\n3. **Define it** — point `defineSoftware()` at the packaged directory:\n\n \u003CCodeSnippet file=\"examples/software/quickstart-node/my-tool.ts\" />\n\n4. **Use it** — pass it to a VM; `my-tool` is now on `$PATH`:\n\n \u003CCodeSnippet file=\"examples/software/quickstart-node/index.ts\" />\n\n\u003C/Steps>\n\n### Agent\n\nAn agent is a Node.js or WASM package (packaged exactly as above) whose `agentos-package.json` carries an **`agent` block** naming a `bin/` command that speaks ACP over stdio.\n\n\u003CSteps>\n\n1. **You have** an npm package with a `bin/` command that speaks ACP over stdio.\n\n2. **Package it** — same `pack` as Node.js, with `--agent` naming the ACP entrypoint. That writes the `agent` block into the package's `agentos-package.json`:\n\n ```bash\n npx @rivet-dev/agentos-toolchain pack @scope/my-agent --out ./packages --agent my-agent-acp\n # → ./packages/my-agent/current (its agentos-package.json now has the agent block)\n ```\n\n3. **Define it** — point `defineSoftware()` at the packaged directory; the agent block is already in its `agentos-package.json`:\n\n \u003CCodeSnippet file=\"examples/software/quickstart-agent/my-agent.ts\" />\n\n4. **Use it** — `createSession()` launches the agent by spawning its `acpEntrypoint`:\n\n \u003CCodeSnippet file=\"examples/software/quickstart-agent/index.ts\" />\n\n\u003C/Steps>\n\n## Reference\n\n### The descriptor\n\nA software entry is just a pointer to the packed package:\n\n```ts\ndefineSoftware({\n packagePath: string, // absolute host path to the packed .aospkg\n // (or a package directory, for local development)\n})\n```\n\nThe normal `packagePath` is the `dist/package.aospkg` that `agentos-toolchain build`/`pack` emit —\na single file holding the package manifest, a precomputed mount index, and the package's mount tar.\nA directory is accepted for local development; it must contain **only the package** — a\n`package.json` with a `bin` map, the runtime files (`bin/`, a flat `node_modules`), and an\n`agentos-package.json`. It is mounted read-only, so **don't point it at a source root**: that drags\n`src/`, dev `node_modules/`, `tsconfig`, and build caches into the VM.\n\n\u003CNote>\n`pack` already emits the packed `.aospkg`. For a package you build by hand (e.g. compiled WASM),\nrun `agentos-toolchain build` to assemble `dist/package/` and pack `dist/package.aospkg`, then\npoint `packagePath` there — never at the workspace root:\n\n```ts\nconst packagePath = resolve(import.meta.dirname, \"dist/package.aospkg\");\nexport default defineSoftware({ packagePath });\n```\n\u003C/Note>\n\n### `agentos-package.json`\n\nThe package's name, optional agent block, and any files/env it provides are authored in an\n`agentos-package.json` at the package root. It is **toolchain input**: at pack time it is compiled\ninto the `.aospkg`'s embedded manifest (which is what the sidecar reads) and stripped from the\npacked files, so the JSON never ships into the VM and the metadata never travels on the wire. For\ncommand/WASM packages it is **generated** for you (name from `package.json`); for agents you\nauthor the `agent` block (or `agentos-toolchain pack --agent \u003Ccmd>` writes it).\n\n```jsonc\n{\n \"name\": \"my-agent\", // → /opt/agentos/\u003Cname>\n \"agent\": { // optional — also exposes an agent session\n \"acpEntrypoint\": \"my-agent-acp\", // bin/ command that speaks ACP over stdio\n \"env\": { }, // static env for the adapter\n \"launchArgs\": [],\n \"snapshot\": false // SDK snapshot optimization\n },\n \"provides\": { // optional — files + env the package contributes\n \"env\": { \"EXAMPLE_HOME\": \"/opt/agentos/my-agent\" },\n \"files\": [{ \"source\": \"etc/example.conf\", \"target\": \"/etc/example.conf\" }]\n }\n}\n```\n\n- **`name`** — the package name; commands and the package mount under `/opt/agentos/\u003Cname>`.\n- **`agent.acpEntrypoint`** — the `bin/` command spawned to start a session; speaks ACP over stdio.\n- **`agent.env`** — static env vars for the adapter, merged under the user env. Every command is on `$PATH`, so point at one directly, e.g. `{ \"PI_ACP_PI_COMMAND\": \"/opt/agentos/bin/pi\" }` so `pi-acp` can spawn the `pi` CLI.\n- **`agent.launchArgs`** — extra CLI args prepended when launching the adapter.\n- **`agent.snapshot`** (default `false`) — load the SDK [once per sidecar](#sdk-snapshotting--snapshot-safety) via a shared V8 heap snapshot instead of per session. Falls back to per-session loading if the SDK isn't snapshot-safe, so it only affects startup latency.\n- **`provides.env`** — env vars merged into the VM's base environment (existing values win — a package never clobbers the user env).\n- **`provides.files`** — read-only files overlaid into the VM filesystem. Each `{ source, target }` maps a path **inside the package** to an **absolute VM path**; the sidecar mounts them as zero-copy read-only lower layers (a guest write copies-up, never touching the host). A missing `source` is a fatal packaging error.\n\n## Advanced\n\n### Meta-packages\n\nA software entry may be an **array** of descriptors, so one package can bundle several. Pass arrays directly to `software`:\n\n```ts\nconst vm = agentOS({\n software: [pi, buildEssential /* = [coreutils, make, git, curl] */],\n});\n```\n\n### SDK snapshotting & snapshot-safety\n\nA V8 heap snapshot freezes the heap *after* the SDK's modules are evaluated, then seeds each new session's isolate from it. This works only if the SDK's **module-init** code (everything that runs at `import`/`require` time) doesn't:\n\n- Create **native handles** — load a `.node` addon, instantiate WebAssembly, or produce a V8 `External`/`Foreign` at top level.\n- Open a **file descriptor, socket, timer, or worker**, or leave a **pending promise**.\n- Bake in **non-deterministic or per-session state** — `process.env`, cwd, `Date.now()`, `Math.random()`, a UUID.\n\nDefer all of the above behind functions or lazy `import()` that run per session. Leave `agent.snapshot: false` for any SDK that can't — the agent still runs, just without the speedup.\n\n## Next steps\n\n- [Custom Agents](/docs/agents/custom): the agent-focused guide.\n- [Building Binaries](/docs/custom-software/building-wasm): compile WASM commands and use the registry.\n- [Packages & command resolution](/docs/architecture/packages-and-command-resolution): how packages mount and resolve.\n- [Request Software](https://github.com/rivet-dev/agentos/issues/new/choose): ask for a package you need.","src/content/docs/docs/custom-software/definition.mdx","9b277218b932df2d","docs/custom-software/publishing",{"id":434,"data":436,"body":439,"filePath":440,"digest":441,"deferredRender":16},{"title":437,"description":438},"Publishing Packages","Build, publish, and consume agentOS packages — locally, from npm, or from your own repo.","agentOS packages — WASM command sets and packed JS agents alike — go through one lifecycle, owned by the **`@rivet-dev/agentos-toolchain`** CLI. This page covers the full flow: building a package, publishing it to npm, and wiring a consumer at either a published version or a local checkout.\n\n## The lifecycle\n\nEvery package is an npm package whose default export points at a self-contained runtime dir (`dist/package/`) that the sidecar projects under `/opt/agentos/\u003Cname>/\u003Cversion>`. The toolchain provides four subcommands:\n\n| Command | What it does |\n|---|---|\n| `stage --commands-dir \u003Cdir>` | Populate `bin/` from a directory of compiled binaries, per the `commands` / `aliases` / `stubs` lists in the package's `agentos-package.json`. |\n| `build` | Assemble `dist/package/` from `bin/` (+ optional `share/`) and pack it into `dist/package.aospkg` — the runtime artifact with the embedded manifest (the `agentos-package.json` is pack-time input, not shipped). |\n| `pack` | Build a self-contained node-closure package from an npm package or local dir (JS agents; validates headers, rejects native addons). |\n| `publish` | Publish the built package to npm. Dist-tag is **`dev` by default**; the `latest` pointer only moves with an explicit `--latest`. |\n\n## Building\n\nIn the AgentOS registry, the `just` recipes drive the toolchain (see [Building Binaries](/docs/custom-software/building-wasm)):\n\n```bash\njust registry-native # compile the native wasm binaries (once per checkout)\njust registry-build # stage + assemble every registry package\njust registry-build coreutils # ... or one package\njust registry-status # inspect: version, staged bin/, assembled dist\n```\n\n## Publishing\n\nRegistry packages **version independently** — each package carries its own semver in its `package.json`. Bump and commit the version, then:\n\n```bash\njust registry-publish coreutils # publish under dist-tag `dev`\njust registry-publish coreutils my-branch # ... under a custom tag\njust registry-publish coreutils latest # DELIBERATE release: moves `latest`\njust registry-publish-all # every built software package, tag `dev`\n```\n\nConsumers installing `@agentos-software/\u003Cname>` with no tag resolve `latest`, so `latest` is reserved for deliberate releases — a dev publish can never clobber what users install.\n\n## Consuming published packages\n\nIn agent-os, the `@agentos-software/*` packages are pinned **per-package** in the workspace catalog. Manage the pins with the `just` recipes (never hand-edit them):\n\n```bash\njust agentos-pkgs-status # current mode + pinned versions\njust agentos-pkgs-set-version coreutils 0.3.1 # pin one package\njust agentos-pkgs-update # re-pin all from the `latest` dist-tag\njust agentos-pkgs-update dev # ... or from another tag\n```\n\n## Local development\n\nAgentOS consumes local registry builds by default because the registry packages\nare pnpm workspace members. Build the native commands with `just registry-native`\nand assemble packages with `just registry-build`; no sibling checkout or\npublished package is required while iterating.\n\nPublished-version pins exist only in release validation and downstream\nconsumers. The AgentOS workspace itself stays self-contained.\n\n## Publishing from your own repo\n\nThe toolchain is not registry-specific — any repo can produce and publish agentOS packages with `npx @rivet-dev/agentos-toolchain`:\n\n```bash\n# a package dir with package.json + agentos-package.json + your compiled binaries\nnpx @rivet-dev/agentos-toolchain stage --commands-dir ./build/wasm\nnpx @rivet-dev/agentos-toolchain build\nnpx @rivet-dev/agentos-toolchain publish --tag dev # or --latest for a release\n```\n\nFor a JS agent, `pack` replaces `stage`/`build`:\n\n```bash\nnpx @rivet-dev/agentos-toolchain pack . --out dist/package --agent my-acp-entrypoint\n```\n\nThe published package is a plain npm dependency — consumers import its descriptor and pass it to `software` exactly like the registry packages. See [Software Definition](/docs/custom-software/definition) for the descriptor shape.","src/content/docs/docs/custom-software/publishing.mdx","8ef166f820dd755b","cookbooks/agent-to-agent",{"id":442,"data":444,"body":447,"digest":448,"rendered":449},{"title":445,"description":446},"Agent to Agent","Bridge two isolated agent VMs: a writer agent calls a reviewer agent through a binding.","\nRun two agents in separate isolated VMs and let one delegate to the other. The writer agent produces code, then hands it to a reviewer agent for feedback — without the two VMs ever sharing a filesystem. Reach for this when you want specialized agents that collaborate but stay isolated.\n\n## How it works\n\nBoth agents are independent `agentOS` VMs registered under one `setup`. The writer is given a `review` binding: a host-side tool the agent can invoke by name. When the writer runs `agentos-review submit --path ...`, the binding's `execute` runs on the host, where it reads the file out of the writer's VM, copies it into the reviewer's VM, opens a reviewer session, and prompts the reviewer to review the code. The review text is returned to the writer as the binding's result. The two VMs never touch directly — the host bridge is the only path between them.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start both agent VMs\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # drive the writer, which calls the reviewer\n```\n\nThe writer writes an API, submits it through the binding, and the reviewer's feedback comes back inline.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/agent-to-agent)\n","a205e7ced7207aa0",{"html":450,"metadata":451},"\u003Cp>Run two agents in separate isolated VMs and let one delegate to the other. The writer agent produces code, then hands it to a reviewer agent for feedback — without the two VMs ever sharing a filesystem. Reach for this when you want specialized agents that collaborate but stay isolated.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Both agents are independent \u003Ccode>agentOS\u003C/code> VMs registered under one \u003Ccode>setup\u003C/code>. The writer is given a \u003Ccode>review\u003C/code> binding: a host-side tool the agent can invoke by name. When the writer runs \u003Ccode>agentos-review submit --path ...\u003C/code>, the binding’s \u003Ccode>execute\u003C/code> runs on the host, where it reads the file out of the writer’s VM, copies it into the reviewer’s VM, opens a reviewer session, and prompts the reviewer to review the code. The review text is returned to the writer as the binding’s result. The two VMs never touch directly — the host bridge is the only path between them.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start both agent VMs\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # drive the writer, which calls the reviewer\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start both agent VMs\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # drive the writer, which calls the reviewer\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start both agent VMs\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # drive the writer, which calls the reviewer\n\u003C/code>\u003C/pre>\n\u003Cp>The writer writes an API, submits it through the binding, and the reviewer’s feedback comes back inline.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/agent-to-agent\">View source on GitHub\u003C/a>\u003C/p>",{"headings":452,"localImagePaths":463,"remoteImagePaths":464,"frontmatter":465},[453,457,460],{"depth":454,"slug":455,"text":456},2,"how-it-works","How it works",{"depth":454,"slug":458,"text":459},"run-it","Run it",{"depth":454,"slug":461,"text":462},"source","Source",[],[],{},"cookbooks/approvals",{"id":466,"data":468,"body":470,"digest":471,"rendered":472},{"title":23,"description":469},"Handle live permission requests with auto-approve and selective-approval flows.","\nWhen an agent wants to read a file, write output, or run a command, the VM raises a permission request. This example shows how to handle those requests—either fully server-side (auto-approve) or by forwarding them to a client for a human-in-the-loop decision (selective approval). Reach for this when you need to control what an agent is allowed to do mid-session.\n\n## How it works\n\nPermissions flow through two complementary hooks:\n\n- **Server-side (`onPermissionRequest`)**: a hook on `agentOS({ ... })` runs for every request before it reaches any client. Inspect `request.description` and `request.params` to approve, log, or filter requests in fully automated pipelines—no client round-trip needed.\n- **Client-side (`permissionRequest` event)**: requests the server forwards reach the client over a live `agent.connect()` connection. The client decides and calls `agent.respondPermission(sessionId, permissionId, \"once\" | \"reject\")` to allow a single action or deny it.\n\nThe `selective` variants combine both: the server handles some requests itself and forwards the rest to the client. A local `pi` software fixture stands in for a real agent package so the example runs self-contained.\n\n## Run it\n\n```bash\nnpm install\n# Auto-approve everything server-side:\nnpx tsx server.ts # in one terminal\nnpx tsx client.ts # in another\n\n# Or run the auto-approve / selective variants:\nnpx tsx auto-approve.ts & npx tsx auto-approve-client.ts\nnpx tsx selective.ts & npx tsx selective-client.ts\n```\n\nThe agent runs its prompt and each permission request is approved, rejected, or logged according to the hook you chose.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/approvals)\n","25f8ea596bbebdb9",{"html":473,"metadata":474},"\u003Cp>When an agent wants to read a file, write output, or run a command, the VM raises a permission request. This example shows how to handle those requests—either fully server-side (auto-approve) or by forwarding them to a client for a human-in-the-loop decision (selective approval). Reach for this when you need to control what an agent is allowed to do mid-session.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Permissions flow through two complementary hooks:\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Server-side (\u003Ccode>onPermissionRequest\u003C/code>)\u003C/strong>: a hook on \u003Ccode>agentOS({ ... })\u003C/code> runs for every request before it reaches any client. Inspect \u003Ccode>request.description\u003C/code> and \u003Ccode>request.params\u003C/code> to approve, log, or filter requests in fully automated pipelines—no client round-trip needed.\u003C/li>\n\u003Cli>\u003Cstrong>Client-side (\u003Ccode>permissionRequest\u003C/code> event)\u003C/strong>: requests the server forwards reach the client over a live \u003Ccode>agent.connect()\u003C/code> connection. The client decides and calls \u003Ccode>agent.respondPermission(sessionId, permissionId, \"once\" | \"reject\")\u003C/code> to allow a single action or deny it.\u003C/li>\n\u003C/ul>\n\u003Cp>The \u003Ccode>selective\u003C/code> variants combine both: the server handles some requests itself and forwards the rest to the client. A local \u003Ccode>pi\u003C/code> software fixture stands in for a real agent package so the example runs self-contained.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\n# Auto-approve everything server-side:\nnpx tsx server.ts # in one terminal\nnpx tsx client.ts # in another\n\n# Or run the auto-approve / selective variants:\nnpx tsx auto-approve.ts & npx tsx auto-approve-client.ts\nnpx tsx selective.ts & npx tsx selective-client.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># Auto-approve everything server-side:\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in one terminal\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in another\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># Or run the auto-approve / selective variants:\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> auto-approve.ts\u003C/span>\u003Cspan style="color:#BFBDB6B3"> &#x26;\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> auto-approve-client.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> selective.ts\u003C/span>\u003Cspan style="color:#BFBDB6B3"> &#x26;\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> selective-client.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\n# Auto-approve everything server-side:\nnpx tsx server.ts # in one terminal\nnpx tsx client.ts # in another\n\n# Or run the auto-approve / selective variants:\nnpx tsx auto-approve.ts & npx tsx auto-approve-client.ts\nnpx tsx selective.ts & npx tsx selective-client.ts\n\u003C/code>\u003C/pre>\n\u003Cp>The agent runs its prompt and each permission request is approved, rejected, or logged according to the hook you chose.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/approvals\">View source on GitHub\u003C/a>\u003C/p>",{"headings":475,"localImagePaths":479,"remoteImagePaths":480,"frontmatter":481},[476,477,478],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/authentication",{"id":482,"data":484,"body":486,"digest":487,"rendered":488},{"title":39,"description":485},"Validate client credentials server-side with onBeforeConnect connection hooks.","\n# Authentication\n\nGate access to your VMs by validating client credentials on the server before any connection is established. Reach for this whenever clients must present a token (a JWT, API key, or session ID) that you verify before letting them create sessions or send prompts.\n\n## How it works\n\nThe client passes credentials as connection `params` when it calls `getOrCreate`. Those params are forwarded to the server, where an `onBeforeConnect` hook inspects them and rejects the connection by throwing. Because `params` is typed as `unknown` on the wire, the hook is the real enforcement point: it checks the token's shape and validity (signature, lookup, expiry) and either returns to admit the connection or throws to deny it. Once admitted, every action on the handle runs against that authenticated connection.\n\n## Run it\n\n```bash\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # in one terminal\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # in another\n```\n\nA client with a valid `authToken` connects and lists the working directory; one with a missing or empty token is rejected before any session is created.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/authentication)\n","7145f36207c2a996",{"html":489,"metadata":490},"\u003Ch1 id=\"authentication\">Authentication\u003C/h1>\n\u003Cp>Gate access to your VMs by validating client credentials on the server before any connection is established. Reach for this whenever clients must present a token (a JWT, API key, or session ID) that you verify before letting them create sessions or send prompts.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The client passes credentials as connection \u003Ccode>params\u003C/code> when it calls \u003Ccode>getOrCreate\u003C/code>. Those params are forwarded to the server, where an \u003Ccode>onBeforeConnect\u003C/code> hook inspects them and rejects the connection by throwing. Because \u003Ccode>params\u003C/code> is typed as \u003Ccode>unknown\u003C/code> on the wire, the hook is the real enforcement point: it checks the token’s shape and validity (signature, lookup, expiry) and either returns to admit the connection or throws to deny it. Once admitted, every action on the handle runs against that authenticated connection.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # in one terminal\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # in another\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in one terminal\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in another\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # in one terminal\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # in another\n\u003C/code>\u003C/pre>\n\u003Cp>A client with a valid \u003Ccode>authToken\u003C/code> connects and lists the working directory; one with a missing or empty token is rejected before any session is created.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/authentication\">View source on GitHub\u003C/a>\u003C/p>",{"headings":491,"localImagePaths":498,"remoteImagePaths":499,"frontmatter":500},[492,495,496,497],{"depth":493,"slug":494,"text":39},1,"authentication",{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/bindings",{"id":501,"data":503,"body":505,"digest":506,"rendered":507},{"title":55,"description":504},"Expose host functions to the agent as CLI commands via Zod-typed bindings.","\nGive an agent access to your own host code—API calls, database lookups, internal services—without writing a tool from scratch. Reach for bindings when the agent needs to call back into your application and you want type-safe inputs plus an auto-generated CLI surface inside the VM.\n\n## How it works\n\nA binding group bundles a `name`, a `description`, and a map of named tools. Each tool declares a Zod `inputSchema`, an `execute` handler that runs on the host, and optional `examples`. You pass the groups to `agentOS({ toolKits: [...] })`, and Agent OS exposes every group to the agent as a CLI command at `/usr/local/bin/agentos-{name}` inside the VM. When the agent invokes the command, the Zod schema validates the arguments and the handler executes host-side, returning the result back to the guest. The client side stays thin: create a session and send a prompt, and the agent decides when to call the binding.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts\n# in another terminal:\nnpx tsx client.ts\n```\n\nThe agent receives the prompt, calls the `weather` forecast binding, and answers using the host-side result.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/bindings)\n","1a8b55da59db2fdd",{"html":508,"metadata":509},"\u003Cp>Give an agent access to your own host code—API calls, database lookups, internal services—without writing a tool from scratch. Reach for bindings when the agent needs to call back into your application and you want type-safe inputs plus an auto-generated CLI surface inside the VM.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A binding group bundles a \u003Ccode>name\u003C/code>, a \u003Ccode>description\u003C/code>, and a map of named tools. Each tool declares a Zod \u003Ccode>inputSchema\u003C/code>, an \u003Ccode>execute\u003C/code> handler that runs on the host, and optional \u003Ccode>examples\u003C/code>. You pass the groups to \u003Ccode>agentOS({ toolKits: [...] })\u003C/code>, and Agent OS exposes every group to the agent as a CLI command at \u003Ccode>/usr/local/bin/agentos-{name}\u003C/code> inside the VM. When the agent invokes the command, the Zod schema validates the arguments and the handler executes host-side, returning the result back to the guest. The client side stays thin: create a session and send a prompt, and the agent decides when to call the binding.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts\n# in another terminal:\nnpx tsx client.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># in another terminal:\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts\n# in another terminal:\nnpx tsx client.ts\n\u003C/code>\u003C/pre>\n\u003Cp>The agent receives the prompt, calls the \u003Ccode>weather\u003C/code> forecast binding, and answers using the host-side result.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/bindings\">View source on GitHub\u003C/a>\u003C/p>",{"headings":510,"localImagePaths":514,"remoteImagePaths":515,"frontmatter":516},[511,512,513],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/browser-terminal",{"id":517,"data":519,"body":522,"digest":523,"rendered":524},{"title":520,"description":521},"Browser Terminal","A full xterm.js terminal in the browser for Agent OS VMs, driving PTY shells over the shipped agentOS() RivetKit actor with a VM sidebar, tabs, and reconnect.","\nA full terminal for Agent OS VMs that runs in the browser, talking to the shipped\nAgent OS actor (`agentOS()` from `@rivet-dev/agentos`) over its live\n[RivetKit](https://rivetkit.org) connection — no bespoke WebSocket server.\n\n- **Left sidebar** — a list of VMs. Each is one Agent OS VM (one RivetKit actor\n instance). The VM ids are kept in `localStorage`, so reopening the page — or\n clicking a VM again — reconnects to the same running VM.\n- **Tabs** — each VM can have multiple terminal sessions (PTY shells).\n- **Reconnect** — the actor keeps its VM (and shells) alive, so a browser that\n reconnects re-adopts the running shells (by the ids it saved in `localStorage`)\n and resumes their live I/O.\n\n## How it works\n\n```\nBrowser (React + xterm.js) Node (server.ts)\n ├─ useActor({ name:\"shellVm\", key }) ├─ agentOS({ software:[…] })\n ├─ openShell / writeShell / resize ──────▶│ setup({ use:{ shellVm } })\n ├─ closeShell │ registry.start()\n └─ conn.on(\"shellData\"|\"shellStderr\"| │\n \"shellExit\") ◀─────────────────────┘ openShell ─▶ broadcast shellData/…\n```\n\nThe browser opens a shell with `openShell`, sends keystrokes with `writeShell`,\nand renders output delivered as `shellData` / `shellStderr` broadcast **events**\n(routed to the right tab by `shellId`, with a small buffer for output that arrives\nbefore a tab subscribes). This mirrors the actor terminal in\n`packages/shell/src/actor-vm.ts`. The VM and its shells live inside the actor's\nRust plugin, so there is no server-side terminal code here — `registry.start()`\nhosts the actor and the browser talks to it directly.\n\n## Run\n\nFrom the repo root:\n\n```bash\npnpm install\npnpm --filter @rivet-dev/agentos-example-browser-terminal dev\n```\n\nor from this directory:\n\n```bash\npnpm dev # RivetKit server (:6420) + Vite (:5173)\n```\n\nOpen http://localhost:5173, click **+ New VM**, then **+** to open a terminal and\nstart typing (`ls`, `echo hi | tr a-z A-Z`, `cd /tmp`, …).\n\nRun the pieces separately if you prefer:\n\n```bash\npnpm server # registry.start() on :6420\npnpm web # Vite dev server on :5173\n```\n\nOverride the web→server endpoint with `VITE_AGENTOS_ENDPOINT` (default\n`http://localhost:6420`).\n\n## Notes\n\n- Software: `@agentos-software/common` (provides `sh` + coreutils) plus `git`,\n `curl`, `ripgrep`, `jq`, and `sqlite3`. Agent OS has no vim/editor package, so\n there is no in-VM editor.\n- The shipped actor has no `listShells` action and keeps no server-side\n scrollback, so reconnect re-adopts saved shell ids and resumes **live** output\n only (history from before the reload is not replayed). Stale ids (VM recreated)\n are dropped after a liveness probe.\n- The VM shell is line-buffered (it only echoes a line on Enter), so the client\n does **local echo + line editing** (printable chars, Backspace, Ctrl-C) and\n suppresses the shell's own echo of the submitted line to avoid double display.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/browser-terminal)\n","efaf4c94fcf02928",{"html":525,"metadata":526},"\u003Cp>A full terminal for Agent OS VMs that runs in the browser, talking to the shipped\nAgent OS actor (\u003Ccode>agentOS()\u003C/code> from \u003Ccode>@rivet-dev/agentos\u003C/code>) over its live\n\u003Ca href=\"https://rivetkit.org\">RivetKit\u003C/a> connection — no bespoke WebSocket server.\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Left sidebar\u003C/strong> — a list of VMs. Each is one Agent OS VM (one RivetKit actor\ninstance). The VM ids are kept in \u003Ccode>localStorage\u003C/code>, so reopening the page — or\nclicking a VM again — reconnects to the same running VM.\u003C/li>\n\u003Cli>\u003Cstrong>Tabs\u003C/strong> — each VM can have multiple terminal sessions (PTY shells).\u003C/li>\n\u003Cli>\u003Cstrong>Reconnect\u003C/strong> — the actor keeps its VM (and shells) alive, so a browser that\nreconnects re-adopts the running shells (by the ids it saved in \u003Ccode>localStorage\u003C/code>)\nand resumes their live I/O.\u003C/li>\n\u003C/ul>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cpre code=\"Browser (React + xterm.js) Node (server.ts)\n ├─ useActor({ name:"shellVm", key }) ├─ agentOS({ software:[…] })\n ├─ openShell / writeShell / resize ──────▶│ setup({ use:{ shellVm } })\n ├─ closeShell │ registry.start()\n └─ conn.on("shellData"|"shellStderr"| │\n "shellExit") ◀─────────────────────┘ openShell ─▶ broadcast shellData/…\n\" language=\"text\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan>Browser (React + xterm.js) Node (server.ts)\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan> ├─ useActor({ name:"shellVm", key }) ├─ agentOS({ software:[…] })\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan> ├─ openShell / writeShell / resize ──────▶│ setup({ use:{ shellVm } })\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan> ├─ closeShell │ registry.start()\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan> └─ conn.on("shellData"|"shellStderr"| │\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan> "shellExit") ◀─────────────────────┘ openShell ─▶ broadcast shellData/…\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan>\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode>Browser (React + xterm.js) Node (server.ts)\n ├─ useActor({ name:\"shellVm\", key }) ├─ agentOS({ software:[…] })\n ├─ openShell / writeShell / resize ──────▶│ setup({ use:{ shellVm } })\n ├─ closeShell │ registry.start()\n └─ conn.on(\"shellData\"|\"shellStderr\"| │\n \"shellExit\") ◀─────────────────────┘ openShell ─▶ broadcast shellData/…\n\u003C/code>\u003C/pre>\n\u003Cp>The browser opens a shell with \u003Ccode>openShell\u003C/code>, sends keystrokes with \u003Ccode>writeShell\u003C/code>,\nand renders output delivered as \u003Ccode>shellData\u003C/code> / \u003Ccode>shellStderr\u003C/code> broadcast \u003Cstrong>events\u003C/strong>\n(routed to the right tab by \u003Ccode>shellId\u003C/code>, with a small buffer for output that arrives\nbefore a tab subscribes). This mirrors the actor terminal in\n\u003Ccode>packages/shell/src/actor-vm.ts\u003C/code>. The VM and its shells live inside the actor’s\nRust plugin, so there is no server-side terminal code here — \u003Ccode>registry.start()\u003C/code>\nhosts the actor and the browser talks to it directly.\u003C/p>\n\u003Ch2 id=\"run\">Run\u003C/h2>\n\u003Cp>From the repo root:\u003C/p>\n\u003Cpre language=\"bash\" code=\"pnpm install\npnpm --filter @rivet-dev/agentos-example-browser-terminal dev\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">pnpm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">pnpm\u003C/span>\u003Cspan style="color:#95E6CB"> --filter\u003C/span>\u003Cspan style="color:#AAD94C"> @rivet-dev/agentos-example-browser-terminal\u003C/span>\u003Cspan style="color:#AAD94C"> dev\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">pnpm install\npnpm --filter @rivet-dev/agentos-example-browser-terminal dev\n\u003C/code>\u003C/pre>\n\u003Cp>or from this directory:\u003C/p>\n\u003Cpre language=\"bash\" code=\"pnpm dev # RivetKit server (:6420) + Vite (:5173)\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">pnpm\u003C/span>\u003Cspan style="color:#AAD94C"> dev\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # RivetKit server (:6420) + Vite (:5173)\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">pnpm dev # RivetKit server (:6420) + Vite (:5173)\n\u003C/code>\u003C/pre>\n\u003Cp>Open \u003Ca href=\"http://localhost:5173\">http://localhost:5173\u003C/a>, click \u003Cstrong>+ New VM\u003C/strong>, then \u003Cstrong>+\u003C/strong> to open a terminal and\nstart typing (\u003Ccode>ls\u003C/code>, \u003Ccode>echo hi | tr a-z A-Z\u003C/code>, \u003Ccode>cd /tmp\u003C/code>, …).\u003C/p>\n\u003Cp>Run the pieces separately if you prefer:\u003C/p>\n\u003Cpre language=\"bash\" code=\"pnpm server # registry.start() on :6420\npnpm web # Vite dev server on :5173\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">pnpm\u003C/span>\u003Cspan style="color:#AAD94C"> server\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # registry.start() on :6420\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">pnpm\u003C/span>\u003Cspan style="color:#AAD94C"> web\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # Vite dev server on :5173\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">pnpm server # registry.start() on :6420\npnpm web # Vite dev server on :5173\n\u003C/code>\u003C/pre>\n\u003Cp>Override the web→server endpoint with \u003Ccode>VITE_AGENTOS_ENDPOINT\u003C/code> (default\n\u003Ccode>http://localhost:6420\u003C/code>).\u003C/p>\n\u003Ch2 id=\"notes\">Notes\u003C/h2>\n\u003Cul>\n\u003Cli>Software: \u003Ccode>@agentos-software/common\u003C/code> (provides \u003Ccode>sh\u003C/code> + coreutils) plus \u003Ccode>git\u003C/code>,\n\u003Ccode>curl\u003C/code>, \u003Ccode>ripgrep\u003C/code>, \u003Ccode>jq\u003C/code>, and \u003Ccode>sqlite3\u003C/code>. Agent OS has no vim/editor package, so\nthere is no in-VM editor.\u003C/li>\n\u003Cli>The shipped actor has no \u003Ccode>listShells\u003C/code> action and keeps no server-side\nscrollback, so reconnect re-adopts saved shell ids and resumes \u003Cstrong>live\u003C/strong> output\nonly (history from before the reload is not replayed). Stale ids (VM recreated)\nare dropped after a liveness probe.\u003C/li>\n\u003Cli>The VM shell is line-buffered (it only echoes a line on Enter), so the client\ndoes \u003Cstrong>local echo + line editing\u003C/strong> (printable chars, Backspace, Ctrl-C) and\nsuppresses the shell’s own echo of the submitted line to avoid double display.\u003C/li>\n\u003C/ul>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/browser-terminal\">View source on GitHub\u003C/a>\u003C/p>",{"headings":527,"localImagePaths":536,"remoteImagePaths":537,"frontmatter":538},[528,529,532,535],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":530,"text":531},"run","Run",{"depth":454,"slug":533,"text":534},"notes","Notes",{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/browserbase",{"id":539,"data":541,"body":543,"digest":544,"rendered":545},{"title":542,"description":-1},"browserbase","# Browserbase example\n\nRead the web from an agentOS VM using the Browserbase [`browse`](https://docs.browserbase.com) CLI.\n\n`browse cloud fetch \u003Curl>` retrieves a page through the Browserbase cloud — the page is rendered by a\nreal browser in Browserbase's infrastructure and returned as JSON with the page content as clean\nmarkdown, so the VM never runs a local browser and no sandbox is required. The CLI ships as the\n`@agentos-software/browserbase` package and is exposed inside the VM as the `browse` command.\n\n`server.ts` defines the agentOS VM (with the Claude Code agent and the `browse` CLI). `client.ts` connects\nto it and shows both ways to use `browse`: running the CLI yourself through the VM's process API, then\nletting a Claude Code agent use it. For the agent, the server mounts the local `skills/` folder into Claude\nCode's skills directory (`~/.claude/skills`). It holds the [`browse` CLI skill](https://github.com/browserbase/stagehand/tree/main/packages/cli)\nthat Browserbase bundles (the one `browse skills install` installs), copied verbatim, so the agent\ndiscovers `browse` and reaches for it on its own; the prompt never mentions it.\n\n## Setup\n\nGet an API key and project id from the [Browserbase dashboard](https://www.browserbase.com/settings), then:\n\n```bash\nexport BROWSERBASE_API_KEY=bb_...\nexport BROWSERBASE_PROJECT_ID=...\nexport ANTHROPIC_API_KEY=sk-ant-... # for the Claude Code agent (client.ts only)\n```\n\n## Run\n\nStart the server, then run a client against it:\n\n```bash\npnpm start # start the agentOS server (server.ts)\npnpm client # run browse directly, then let a Claude Code agent use it\n```\n\n## Interactive browsing\n\n`browse`'s interactive driver commands (`browse open`, `browse snapshot`, `browse click`, …) drive a\nlive browser session step by step. That mode runs a local driver daemon that the in-VM runtime does\nnot host — for interactive, multi-step browser automation, run `browse` inside a full sandbox via\n[Sandbox Mounting](https://agentos-sdk.dev/docs/sandbox). The `browse cloud` commands used here need\nno daemon and run directly in the VM.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/browserbase)\n","9894972d31b412ff",{"html":546,"metadata":547},"\u003Ch1 id=\"browserbase-example\">Browserbase example\u003C/h1>\n\u003Cp>Read the web from an agentOS VM using the Browserbase \u003Ca href=\"https://docs.browserbase.com\">\u003Ccode>browse\u003C/code>\u003C/a> CLI.\u003C/p>\n\u003Cp>\u003Ccode>browse cloud fetch <url>\u003C/code> retrieves a page through the Browserbase cloud — the page is rendered by a\nreal browser in Browserbase’s infrastructure and returned as JSON with the page content as clean\nmarkdown, so the VM never runs a local browser and no sandbox is required. The CLI ships as the\n\u003Ccode>@agentos-software/browserbase\u003C/code> package and is exposed inside the VM as the \u003Ccode>browse\u003C/code> command.\u003C/p>\n\u003Cp>\u003Ccode>server.ts\u003C/code> defines the agentOS VM (with the Claude Code agent and the \u003Ccode>browse\u003C/code> CLI). \u003Ccode>client.ts\u003C/code> connects\nto it and shows both ways to use \u003Ccode>browse\u003C/code>: running the CLI yourself through the VM’s process API, then\nletting a Claude Code agent use it. For the agent, the server mounts the local \u003Ccode>skills/\u003C/code> folder into Claude\nCode’s skills directory (\u003Ccode>~/.claude/skills\u003C/code>). It holds the \u003Ca href=\"https://github.com/browserbase/stagehand/tree/main/packages/cli\">\u003Ccode>browse\u003C/code> CLI skill\u003C/a>\nthat Browserbase bundles (the one \u003Ccode>browse skills install\u003C/code> installs), copied verbatim, so the agent\ndiscovers \u003Ccode>browse\u003C/code> and reaches for it on its own; the prompt never mentions it.\u003C/p>\n\u003Ch2 id=\"setup\">Setup\u003C/h2>\n\u003Cp>Get an API key and project id from the \u003Ca href=\"https://www.browserbase.com/settings\">Browserbase dashboard\u003C/a>, then:\u003C/p>\n\u003Cpre language=\"bash\" code=\"export BROWSERBASE_API_KEY=bb_...\nexport BROWSERBASE_PROJECT_ID=...\nexport ANTHROPIC_API_KEY=sk-ant-... # for the Claude Code agent (client.ts only)\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#FF8F40">export\u003C/span>\u003Cspan style="color:#BFBDB6"> BROWSERBASE_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#BFBDB6">bb_...\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#FF8F40">export\u003C/span>\u003Cspan style="color:#BFBDB6"> BROWSERBASE_PROJECT_ID\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#BFBDB6">...\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#FF8F40">export\u003C/span>\u003Cspan style="color:#BFBDB6"> ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#BFBDB6">sk-ant-... \u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"># for the Claude Code agent (client.ts only)\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">export BROWSERBASE_API_KEY=bb_...\nexport BROWSERBASE_PROJECT_ID=...\nexport ANTHROPIC_API_KEY=sk-ant-... # for the Claude Code agent (client.ts only)\n\u003C/code>\u003C/pre>\n\u003Ch2 id=\"run\">Run\u003C/h2>\n\u003Cp>Start the server, then run a client against it:\u003C/p>\n\u003Cpre language=\"bash\" code=\"pnpm start # start the agentOS server (server.ts)\npnpm client # run browse directly, then let a Claude Code agent use it\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">pnpm\u003C/span>\u003Cspan style="color:#AAD94C"> start\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the agentOS server (server.ts)\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">pnpm\u003C/span>\u003Cspan style="color:#AAD94C"> client\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # run browse directly, then let a Claude Code agent use it\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">pnpm start # start the agentOS server (server.ts)\npnpm client # run browse directly, then let a Claude Code agent use it\n\u003C/code>\u003C/pre>\n\u003Ch2 id=\"interactive-browsing\">Interactive browsing\u003C/h2>\n\u003Cp>\u003Ccode>browse\u003C/code>’s interactive driver commands (\u003Ccode>browse open\u003C/code>, \u003Ccode>browse snapshot\u003C/code>, \u003Ccode>browse click\u003C/code>, …) drive a\nlive browser session step by step. That mode runs a local driver daemon that the in-VM runtime does\nnot host — for interactive, multi-step browser automation, run \u003Ccode>browse\u003C/code> inside a full sandbox via\n\u003Ca href=\"https://agentos-sdk.dev/docs/sandbox\">Sandbox Mounting\u003C/a>. The \u003Ccode>browse cloud\u003C/code> commands used here need\nno daemon and run directly in the VM.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/browserbase\">View source on GitHub\u003C/a>\u003C/p>",{"headings":548,"localImagePaths":560,"remoteImagePaths":561,"frontmatter":562},[549,552,555,556,559],{"depth":493,"slug":550,"text":551},"browserbase-example","Browserbase example",{"depth":454,"slug":553,"text":554},"setup","Setup",{"depth":454,"slug":530,"text":531},{"depth":454,"slug":557,"text":558},"interactive-browsing","Interactive browsing",{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/claude",{"id":563,"data":565,"body":568,"digest":569,"rendered":570},{"title":566,"description":567},"Claude Agent","Run the Claude Code agent in a session using an Anthropic API key.","\nRun the Claude Code agent inside a VM session and drive it with prompts. Reach for this when you want a coding agent that reads, writes, and runs commands in an isolated environment instead of calling the model API directly.\n\n## How it works\n\nThe server registers a VM with the Claude Code software and starts the registry. The client creates a session against the `claude` agent, passing `ANTHROPIC_API_KEY` through the session env, then calls `sendPrompt` to get the agent's response. From there you can layer on extras: drop a `SKILL.md` into `~/.claude/skills/` before creating the session and the agent discovers it automatically, or pass `mcpServers` (local child processes or remote URLs) to expose more tools. Pre-install local MCP servers with `exec` so first-run `npx` output does not corrupt the stdio handshake.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-ant-... npm run start\n```\n\nThe agent answers the prompt and prints its response to the console.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/claude)\n","8060529e4208a811",{"html":571,"metadata":572},"\u003Cp>Run the Claude Code agent inside a VM session and drive it with prompts. Reach for this when you want a coding agent that reads, writes, and runs commands in an isolated environment instead of calling the model API directly.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The server registers a VM with the Claude Code software and starts the registry. The client creates a session against the \u003Ccode>claude\u003C/code> agent, passing \u003Ccode>ANTHROPIC_API_KEY\u003C/code> through the session env, then calls \u003Ccode>sendPrompt\u003C/code> to get the agent’s response. From there you can layer on extras: drop a \u003Ccode>SKILL.md\u003C/code> into \u003Ccode>~/.claude/skills/\u003C/code> before creating the session and the agent discovers it automatically, or pass \u003Ccode>mcpServers\u003C/code> (local child processes or remote URLs) to expose more tools. Pre-install local MCP servers with \u003Ccode>exec\u003C/code> so first-run \u003Ccode>npx\u003C/code> output does not corrupt the stdio handshake.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-ant-... npm run start\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-ant-...\u003C/span>\u003Cspan style="color:#59C2FF"> npm\u003C/span>\u003Cspan style="color:#AAD94C"> run\u003C/span>\u003Cspan style="color:#AAD94C"> start\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-ant-... npm run start\n\u003C/code>\u003C/pre>\n\u003Cp>The agent answers the prompt and prints its response to the console.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/claude\">View source on GitHub\u003C/a>\u003C/p>",{"headings":573,"localImagePaths":577,"remoteImagePaths":578,"frontmatter":579},[574,575,576],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/codex",{"id":580,"data":582,"body":585,"digest":586,"rendered":587},{"title":583,"description":584},"Codex Agent","Run the Codex agent in a session using an OpenAI API key.","\n# Codex Agent\n\nRun OpenAI's Codex agent inside a VM session and prompt it with natural language. Reach for this when you want a coding agent that can read and act on the VM's filesystem, backed by your own OpenAI API key.\n\n## How it works\n\nRegister the `codex` software with `agentOS({ software: [codex] })` so the VM knows how to launch the agent. The client calls `agent.createSession(\"codex\", ...)`, passing `OPENAI_API_KEY` through `env`, then drives the agent with `agent.sendPrompt`. Two optional extensions build on the same flow: drop a `SKILL.md` into `/home/agentos/.codex/skills/` before creating the session and the agent auto-discovers it, or pass `mcpServers` (local child-process or remote URL) to expose extra tools.\n\n## Run it\n\n```bash\nnpm install\nexport OPENAI_API_KEY=sk-...\nnpx tsx server.ts # starts the registry on http://localhost:6420\nnpx tsx client.ts # creates a Codex session and prints the agent's reply\n```\n\nYou should see the agent describe the files in its working directory.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/codex)\n","c44c8ecdf080f117",{"html":588,"metadata":589},"\u003Ch1 id=\"codex-agent\">Codex Agent\u003C/h1>\n\u003Cp>Run OpenAI’s Codex agent inside a VM session and prompt it with natural language. Reach for this when you want a coding agent that can read and act on the VM’s filesystem, backed by your own OpenAI API key.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Register the \u003Ccode>codex\u003C/code> software with \u003Ccode>agentOS({ software: [codex] })\u003C/code> so the VM knows how to launch the agent. The client calls \u003Ccode>agent.createSession(\"codex\", ...)\u003C/code>, passing \u003Ccode>OPENAI_API_KEY\u003C/code> through \u003Ccode>env\u003C/code>, then drives the agent with \u003Ccode>agent.sendPrompt\u003C/code>. Two optional extensions build on the same flow: drop a \u003Ccode>SKILL.md\u003C/code> into \u003Ccode>/home/agentos/.codex/skills/\u003C/code> before creating the session and the agent auto-discovers it, or pass \u003Ccode>mcpServers\u003C/code> (local child-process or remote URL) to expose extra tools.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nexport OPENAI_API_KEY=sk-...\nnpx tsx server.ts # starts the registry on http://localhost:6420\nnpx tsx client.ts # creates a Codex session and prints the agent's reply\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#FF8F40">export\u003C/span>\u003Cspan style="color:#BFBDB6"> OPENAI_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#BFBDB6">sk-...\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # starts the registry on http://localhost:6420\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # creates a Codex session and prints the agent's reply\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nexport OPENAI_API_KEY=sk-...\nnpx tsx server.ts # starts the registry on http://localhost:6420\nnpx tsx client.ts # creates a Codex session and prints the agent's reply\n\u003C/code>\u003C/pre>\n\u003Cp>You should see the agent describe the files in its working directory.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/codex\">View source on GitHub\u003C/a>\u003C/p>",{"headings":590,"localImagePaths":596,"remoteImagePaths":597,"frontmatter":598},[591,593,594,595],{"depth":493,"slug":592,"text":583},"codex-agent",{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/core",{"id":599,"data":601,"body":604,"digest":605,"rendered":606},{"title":602,"description":603},"Core","Core AgentOs API: exec, config reference, lifecycle events, and mounts.","\nThe core `@rivet-dev/agentos-core` API surface in one place: boot a VM with\n`AgentOs.create()` and drive it directly for exec, filesystem, processes, agent\nsessions, networking, and cron — no actor runtime and no client/server split.\nReach for this when you want a reference of what an `AgentOs` instance can do and\nhow it is configured.\n\n## How it works\n\n`AgentOs.create({ ... })` boots a VM in-process with its mounts, software, and\nnetwork settings, and returns an `AgentOs` instance. Everything runs through that\ninstance: `exec`/`spawn` for processes, `readFile`/`writeFiles`/`readdirRecursive`\nfor the filesystem, `createSession`/`prompt` for agents, `fetch` for in-VM\nservers, and `scheduleCron` for jobs. Process output and session/permission/cron\nevents are delivered through callbacks (`spawn({ onStdout })`, `onProcessExit`,\n`onSessionEvent`, `onPermissionRequest`, `onCronEvent`).\n\n- `vm.ts` — boot a VM and every instance capability (exec, filesystem,\n processes, sessions, networking, cron).\n- `advanced.ts` — pin VMs to a dedicated sidecar process.\n- `config-reference.ts` — the full `AgentOs.create()` config surface.\n- `hooks.ts` — per-session event and permission observation.\n- `mounts.ts` — host-directory and S3 mount descriptors.\n\n## Run it\n\n```sh\nnpm install\nnpx tsx vm.ts\n```\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/core)\n","50e94d5fdd57faad",{"html":607,"metadata":608},"\u003Cp>The core \u003Ccode>@rivet-dev/agentos-core\u003C/code> API surface in one place: boot a VM with\n\u003Ccode>AgentOs.create()\u003C/code> and drive it directly for exec, filesystem, processes, agent\nsessions, networking, and cron — no actor runtime and no client/server split.\nReach for this when you want a reference of what an \u003Ccode>AgentOs\u003C/code> instance can do and\nhow it is configured.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>\u003Ccode>AgentOs.create({ ... })\u003C/code> boots a VM in-process with its mounts, software, and\nnetwork settings, and returns an \u003Ccode>AgentOs\u003C/code> instance. Everything runs through that\ninstance: \u003Ccode>exec\u003C/code>/\u003Ccode>spawn\u003C/code> for processes, \u003Ccode>readFile\u003C/code>/\u003Ccode>writeFiles\u003C/code>/\u003Ccode>readdirRecursive\u003C/code>\nfor the filesystem, \u003Ccode>createSession\u003C/code>/\u003Ccode>prompt\u003C/code> for agents, \u003Ccode>fetch\u003C/code> for in-VM\nservers, and \u003Ccode>scheduleCron\u003C/code> for jobs. Process output and session/permission/cron\nevents are delivered through callbacks (\u003Ccode>spawn({ onStdout })\u003C/code>, \u003Ccode>onProcessExit\u003C/code>,\n\u003Ccode>onSessionEvent\u003C/code>, \u003Ccode>onPermissionRequest\u003C/code>, \u003Ccode>onCronEvent\u003C/code>).\u003C/p>\n\u003Cul>\n\u003Cli>\u003Ccode>vm.ts\u003C/code> — boot a VM and every instance capability (exec, filesystem,\nprocesses, sessions, networking, cron).\u003C/li>\n\u003Cli>\u003Ccode>advanced.ts\u003C/code> — pin VMs to a dedicated sidecar process.\u003C/li>\n\u003Cli>\u003Ccode>config-reference.ts\u003C/code> — the full \u003Ccode>AgentOs.create()\u003C/code> config surface.\u003C/li>\n\u003Cli>\u003Ccode>hooks.ts\u003C/code> — per-session event and permission observation.\u003C/li>\n\u003Cli>\u003Ccode>mounts.ts\u003C/code> — host-directory and S3 mount descriptors.\u003C/li>\n\u003C/ul>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpx tsx vm.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> vm.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpx tsx vm.ts\n\u003C/code>\u003C/pre>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/core\">View source on GitHub\u003C/a>\u003C/p>",{"headings":609,"localImagePaths":613,"remoteImagePaths":614,"frontmatter":615},[610,611,612],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/crash-course",{"id":616,"data":618,"body":620,"digest":621,"rendered":622},{"title":87,"description":619},"Guided tour through core capabilities: sessions, filesystem, processes, networking, cron, permissions, and multiplayer.","\n# Crash Course\n\nA guided tour through the core capabilities of Agent OS, one small client per feature. Reach for it when you want to see the whole surface area — sessions, filesystem, processes, networking, cron, permissions, and multiplayer — without reading the full docs.\n\n## How it works\n\nA single `server.ts` stands up an Agent OS registry with the `pi` agent software, and each `*-client.ts` file connects to it to exercise one capability:\n\n- **Sessions** (`minimal-client.ts`, `sessions-client.ts`) — create a session, stream `sessionEvent`s, send prompts, and read back files the agent wrote.\n- **Filesystem** (`filesystem-client.ts`) — `writeFile`, `readFile`, and recursive directory listing.\n- **Processes** (`processes-client.ts`) — one-shot `exec` plus long-running `spawn` with streamed `processOutput`.\n- **Networking** (`networking-client.ts`) — `vmFetch` against an in-VM service and signed public preview URLs.\n- **Cron** (`cron-client.ts`) — schedule recurring `exec` commands and agent sessions.\n- **Permissions** (`permissions-client.ts`, `permissions-server.ts`) — handle permission requests client-side (human-in-the-loop) or auto-approve server-side.\n- **Multiplayer** (`multiplayer-client.ts`) — two clients observing the same shared agent session.\n- **Agent-to-agent** (`agent-to-agent-*.ts`) — a coder agent calls a `review` binding that drives a separate reviewer agent.\n\n## Run it\n\n```bash\nnpm install\nnpx tsx server.ts # start the registry\nnpx tsx minimal-client.ts # then run any client in another terminal\n```\n\nEach client prints its results — streamed events, file contents, process output, or URLs — to the console.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/crash-course)\n","823449d34f6b2fd2",{"html":623,"metadata":624},"\u003Ch1 id=\"crash-course\">Crash Course\u003C/h1>\n\u003Cp>A guided tour through the core capabilities of Agent OS, one small client per feature. Reach for it when you want to see the whole surface area — sessions, filesystem, processes, networking, cron, permissions, and multiplayer — without reading the full docs.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A single \u003Ccode>server.ts\u003C/code> stands up an Agent OS registry with the \u003Ccode>pi\u003C/code> agent software, and each \u003Ccode>*-client.ts\u003C/code> file connects to it to exercise one capability:\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Sessions\u003C/strong> (\u003Ccode>minimal-client.ts\u003C/code>, \u003Ccode>sessions-client.ts\u003C/code>) — create a session, stream \u003Ccode>sessionEvent\u003C/code>s, send prompts, and read back files the agent wrote.\u003C/li>\n\u003Cli>\u003Cstrong>Filesystem\u003C/strong> (\u003Ccode>filesystem-client.ts\u003C/code>) — \u003Ccode>writeFile\u003C/code>, \u003Ccode>readFile\u003C/code>, and recursive directory listing.\u003C/li>\n\u003Cli>\u003Cstrong>Processes\u003C/strong> (\u003Ccode>processes-client.ts\u003C/code>) — one-shot \u003Ccode>exec\u003C/code> plus long-running \u003Ccode>spawn\u003C/code> with streamed \u003Ccode>processOutput\u003C/code>.\u003C/li>\n\u003Cli>\u003Cstrong>Networking\u003C/strong> (\u003Ccode>networking-client.ts\u003C/code>) — \u003Ccode>vmFetch\u003C/code> against an in-VM service and signed public preview URLs.\u003C/li>\n\u003Cli>\u003Cstrong>Cron\u003C/strong> (\u003Ccode>cron-client.ts\u003C/code>) — schedule recurring \u003Ccode>exec\u003C/code> commands and agent sessions.\u003C/li>\n\u003Cli>\u003Cstrong>Permissions\u003C/strong> (\u003Ccode>permissions-client.ts\u003C/code>, \u003Ccode>permissions-server.ts\u003C/code>) — handle permission requests client-side (human-in-the-loop) or auto-approve server-side.\u003C/li>\n\u003Cli>\u003Cstrong>Multiplayer\u003C/strong> (\u003Ccode>multiplayer-client.ts\u003C/code>) — two clients observing the same shared agent session.\u003C/li>\n\u003Cli>\u003Cstrong>Agent-to-agent\u003C/strong> (\u003Ccode>agent-to-agent-*.ts\u003C/code>) — a coder agent calls a \u003Ccode>review\u003C/code> binding that drives a separate reviewer agent.\u003C/li>\n\u003C/ul>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nnpx tsx server.ts # start the registry\nnpx tsx minimal-client.ts # then run any client in another terminal\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the registry\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> minimal-client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # then run any client in another terminal\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nnpx tsx server.ts # start the registry\nnpx tsx minimal-client.ts # then run any client in another terminal\n\u003C/code>\u003C/pre>\n\u003Cp>Each client prints its results — streamed events, file contents, process output, or URLs — to the console.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/crash-course\">View source on GitHub\u003C/a>\u003C/p>",{"headings":625,"localImagePaths":631,"remoteImagePaths":632,"frontmatter":633},[626,628,629,630],{"depth":493,"slug":627,"text":87},"crash-course",{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/cron",{"id":634,"data":636,"body":639,"digest":640,"rendered":641},{"title":637,"description":638},"Cron","Schedule recurring commands and agent sessions with cron, including overlap handling, monitoring, and cancellation.","\nRun work on a schedule inside a VM — a shell command on a fixed interval, or a recurring agent session that reviews logs, triages issues, or processes a queue. Reach for this when you need background jobs that fire on a cron expression instead of on demand.\n\n## How it works\n\nEach VM handle exposes `scheduleCron({ schedule, action, overlap })`. The `schedule` is a standard cron expression, and the `action` is either an `exec` (run a command with args) or a `session` (spawn an agent of a given `agentType` with a `prompt`). The `overlap` policy decides what happens when a run is still going when the next tick arrives: `skip` drops the new run, `queue` lines it up to run after. Scheduling returns a job `id` you can later pass to `cancelCronJob`, and `listCronJobs` enumerates everything registered on the VM. Connecting to the handle and listening for `cronEvent` streams each run's lifecycle so you can monitor execution.\n\n## Run it\n\n```sh\nnpm install\nnpx tsx server.ts # start the registry, then run any example, e.g. npx tsx schedule-session.ts\n```\n\nYou should see the cron job registered and its `id` printed; scheduled runs fire on their interval and surface as `cronEvent`s.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/cron)\n","a729c2d2adad946a",{"html":642,"metadata":643},"\u003Cp>Run work on a schedule inside a VM — a shell command on a fixed interval, or a recurring agent session that reviews logs, triages issues, or processes a queue. Reach for this when you need background jobs that fire on a cron expression instead of on demand.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Each VM handle exposes \u003Ccode>scheduleCron({ schedule, action, overlap })\u003C/code>. The \u003Ccode>schedule\u003C/code> is a standard cron expression, and the \u003Ccode>action\u003C/code> is either an \u003Ccode>exec\u003C/code> (run a command with args) or a \u003Ccode>session\u003C/code> (spawn an agent of a given \u003Ccode>agentType\u003C/code> with a \u003Ccode>prompt\u003C/code>). The \u003Ccode>overlap\u003C/code> policy decides what happens when a run is still going when the next tick arrives: \u003Ccode>skip\u003C/code> drops the new run, \u003Ccode>queue\u003C/code> lines it up to run after. Scheduling returns a job \u003Ccode>id\u003C/code> you can later pass to \u003Ccode>cancelCronJob\u003C/code>, and \u003Ccode>listCronJobs\u003C/code> enumerates everything registered on the VM. Connecting to the handle and listening for \u003Ccode>cronEvent\u003C/code> streams each run’s lifecycle so you can monitor execution.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpx tsx server.ts # start the registry, then run any example, e.g. npx tsx schedule-session.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the registry, then run any example, e.g. npx tsx schedule-session.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpx tsx server.ts # start the registry, then run any example, e.g. npx tsx schedule-session.ts\n\u003C/code>\u003C/pre>\n\u003Cp>You should see the cron job registered and its \u003Ccode>id\u003C/code> printed; scheduled runs fire on their interval and surface as \u003Ccode>cronEvent\u003C/code>s.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/cron\">View source on GitHub\u003C/a>\u003C/p>",{"headings":644,"localImagePaths":648,"remoteImagePaths":649,"frontmatter":650},[645,646,647],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/filesystem",{"id":651,"data":653,"body":655,"digest":656,"rendered":657},{"title":111,"description":654},"Filesystem access: host-side file APIs, VFS isolation, and mounting memory, host directories, S3, and Google Drive.","\nEvery VM gets an isolated virtual filesystem (VFS) that you drive from the host. Reach for this when you need to seed files into a VM, read results back out, or expose external storage to the guest without leaking the host disk.\n\n## How it works\n\nThe host client exposes file APIs directly on a VM handle — `writeFile`/`readFile` for single files, `writeFiles`/`readFiles` for batches, plus `mkdir`, `readdir`, `readdirRecursive`, `stat`, `exists`, `move`, and `deleteFile`. Bytes you write land in the kernel's in-memory VFS, which the guest sees through the normal `node:fs` API; the real host disk is never exposed, so a path that exists in the VFS does not exist on the host.\n\nTo bridge in external storage, declare `mounts` on `agentOS({ ... })`. Each mount maps a guest path to a plugin: `memory` for scratch space, `host_dir` for a host directory (optionally `readOnly`), `s3` for a bucket/prefix, or `google_drive` for a Drive folder. The guest reads and writes those paths like any other directory.\n\n## Run it\n\n```bash\nnpm install\nnpx tsx server.ts # start the VM host\nnpx tsx operations.ts # in another shell: exercise the file APIs\n```\n\n`operations.ts` writes, reads, lists, moves, and deletes files; `isolation.ts` shows the VFS is sealed from the host disk; the `mount-*.ts` servers swap in different storage backends.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/filesystem)\n","d5bc0a592860aec4",{"html":658,"metadata":659},"\u003Cp>Every VM gets an isolated virtual filesystem (VFS) that you drive from the host. Reach for this when you need to seed files into a VM, read results back out, or expose external storage to the guest without leaking the host disk.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The host client exposes file APIs directly on a VM handle — \u003Ccode>writeFile\u003C/code>/\u003Ccode>readFile\u003C/code> for single files, \u003Ccode>writeFiles\u003C/code>/\u003Ccode>readFiles\u003C/code> for batches, plus \u003Ccode>mkdir\u003C/code>, \u003Ccode>readdir\u003C/code>, \u003Ccode>readdirRecursive\u003C/code>, \u003Ccode>stat\u003C/code>, \u003Ccode>exists\u003C/code>, \u003Ccode>move\u003C/code>, and \u003Ccode>deleteFile\u003C/code>. Bytes you write land in the kernel’s in-memory VFS, which the guest sees through the normal \u003Ccode>node:fs\u003C/code> API; the real host disk is never exposed, so a path that exists in the VFS does not exist on the host.\u003C/p>\n\u003Cp>To bridge in external storage, declare \u003Ccode>mounts\u003C/code> on \u003Ccode>agentOS({ ... })\u003C/code>. Each mount maps a guest path to a plugin: \u003Ccode>memory\u003C/code> for scratch space, \u003Ccode>host_dir\u003C/code> for a host directory (optionally \u003Ccode>readOnly\u003C/code>), \u003Ccode>s3\u003C/code> for a bucket/prefix, or \u003Ccode>google_drive\u003C/code> for a Drive folder. The guest reads and writes those paths like any other directory.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nnpx tsx server.ts # start the VM host\nnpx tsx operations.ts # in another shell: exercise the file APIs\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the VM host\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> operations.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in another shell: exercise the file APIs\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nnpx tsx server.ts # start the VM host\nnpx tsx operations.ts # in another shell: exercise the file APIs\n\u003C/code>\u003C/pre>\n\u003Cp>\u003Ccode>operations.ts\u003C/code> writes, reads, lists, moves, and deletes files; \u003Ccode>isolation.ts\u003C/code> shows the VFS is sealed from the host disk; the \u003Ccode>mount-*.ts\u003C/code> servers swap in different storage backends.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/filesystem\">View source on GitHub\u003C/a>\u003C/p>",{"headings":660,"localImagePaths":664,"remoteImagePaths":665,"frontmatter":666},[661,662,663],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/llm-credentials",{"id":667,"data":669,"body":671,"digest":672,"rendered":673},{"title":141,"description":670},"Pass LLM provider keys per session via env, including a per-tenant credential pattern.","\nA VM never inherits the host `process.env`, so LLM provider keys must be handed to each session explicitly. Reach for this when your agent needs an `ANTHROPIC_API_KEY` (or any provider secret) and you want that key scoped to a single session — or to a single tenant — rather than baked into the server.\n\n## How it works\n\nThe server declares the agent software but holds no credentials. The client passes keys through the `env` option on `createSession`, which injects them into that session's VM only. For multi-tenant setups, give each tenant an isolated VM keyed by their id and resolve their key from your own credential store at session-creation time. Keys live on the server and are never sent to the client.\n\n## Run it\n\n```bash\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # then, in another shell:\nANTHROPIC_API_KEY=sk-... npx tsx client.ts\n```\n\nThe client prints a new session id; the agent inside the VM sees the key via its environment. See `per-tenant.ts` for the per-tenant variant.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/llm-credentials)\n","9aca20bf57234cf7",{"html":674,"metadata":675},"\u003Cp>A VM never inherits the host \u003Ccode>process.env\u003C/code>, so LLM provider keys must be handed to each session explicitly. Reach for this when your agent needs an \u003Ccode>ANTHROPIC_API_KEY\u003C/code> (or any provider secret) and you want that key scoped to a single session — or to a single tenant — rather than baked into the server.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The server declares the agent software but holds no credentials. The client passes keys through the \u003Ccode>env\u003C/code> option on \u003Ccode>createSession\u003C/code>, which injects them into that session’s VM only. For multi-tenant setups, give each tenant an isolated VM keyed by their id and resolve their key from your own credential store at session-creation time. Keys live on the server and are never sent to the client.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # then, in another shell:\nANTHROPIC_API_KEY=sk-... npx tsx client.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # then, in another shell:\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # then, in another shell:\nANTHROPIC_API_KEY=sk-... npx tsx client.ts\n\u003C/code>\u003C/pre>\n\u003Cp>The client prints a new session id; the agent inside the VM sees the key via its environment. See \u003Ccode>per-tenant.ts\u003C/code> for the per-tenant variant.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/llm-credentials\">View source on GitHub\u003C/a>\u003C/p>",{"headings":676,"localImagePaths":680,"remoteImagePaths":681,"frontmatter":682},[677,678,679],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/multiplayer",{"id":683,"data":685,"body":687,"digest":688,"rendered":689},{"title":157,"description":686},"Multiple clients observing one session: shared output, collaborative input, and reconnect replay.","\nRun one agent session and let several clients watch and drive it at once. Reach for this when a session needs more than one viewer — pair programming, a shared review session, or a dashboard mirroring what an agent is doing live.\n\n## How it works\n\nClients connect to the same VM actor by id (`getOrCreate(\"shared-agent\")`), so they share one session rather than spawning their own. Each connection subscribes to the same event stream — `sessionEvent`, `processOutput`, and `shellData` all fan out to every connected client. One client can create the session and `sendPrompt`, while others observe the streaming response without driving it. Because the server fans events out from a single session, the `onSessionEvent` server hook still fires once per event regardless of how many clients are attached. Every event carries a sequence number, so a client that drops can call `getSequencedEvents({ since })` to replay what it missed before resuming the live stream.\n\n## Run it\n\n```sh\nnpm install\n# terminal 1 — start the server\nnpx tsx server.ts\n# terminal 2+ — attach observers / drivers\nnpx tsx collaborative.ts\n```\n\nMultiple clients print the same session events; an observer sees the driver's prompt response stream in real time.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/multiplayer)\n","bc98a070dad72778",{"html":690,"metadata":691},"\u003Cp>Run one agent session and let several clients watch and drive it at once. Reach for this when a session needs more than one viewer — pair programming, a shared review session, or a dashboard mirroring what an agent is doing live.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Clients connect to the same VM actor by id (\u003Ccode>getOrCreate(\"shared-agent\")\u003C/code>), so they share one session rather than spawning their own. Each connection subscribes to the same event stream — \u003Ccode>sessionEvent\u003C/code>, \u003Ccode>processOutput\u003C/code>, and \u003Ccode>shellData\u003C/code> all fan out to every connected client. One client can create the session and \u003Ccode>sendPrompt\u003C/code>, while others observe the streaming response without driving it. Because the server fans events out from a single session, the \u003Ccode>onSessionEvent\u003C/code> server hook still fires once per event regardless of how many clients are attached. Every event carries a sequence number, so a client that drops can call \u003Ccode>getSequencedEvents({ since })\u003C/code> to replay what it missed before resuming the live stream.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\n# terminal 1 — start the server\nnpx tsx server.ts\n# terminal 2+ — attach observers / drivers\nnpx tsx collaborative.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># terminal 1 — start the server\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># terminal 2+ — attach observers / drivers\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> collaborative.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\n# terminal 1 — start the server\nnpx tsx server.ts\n# terminal 2+ — attach observers / drivers\nnpx tsx collaborative.ts\n\u003C/code>\u003C/pre>\n\u003Cp>Multiple clients print the same session events; an observer sees the driver’s prompt response stream in real time.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/multiplayer\">View source on GitHub\u003C/a>\u003C/p>",{"headings":692,"localImagePaths":696,"remoteImagePaths":697,"frontmatter":698},[693,694,695],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/networking",{"id":699,"data":701,"body":703,"digest":704,"rendered":705},{"title":373,"description":702},"VM networking: loopback servers, fetch from inside and outside the VM, and signed preview URLs.","\n# Networking\n\nRun a service inside a VM and reach it — from the client and from the public web. Reach for this when an agent spins up a dev server, API, or web app that you need to call or share.\n\n## How it works\n\nA process inside the VM binds a normal loopback port (e.g. `3000`), exactly like any Node server. The client reaches it with `agent.vmFetch(port, path, options)`, which proxies an HTTP request straight to that loopback port without exposing it to the network. To expose a port beyond loopback, set `loopbackExemptPorts` on the VM config. For external sharing, `agent.createSignedPreviewUrl(port, expiresInSeconds)` mints a short-lived signed URL; the `preview` config sets default and maximum lifetimes, and old tokens fall off automatically as they expire.\n\n## Run it\n\n```bash\nnpm install\n# Start the VM host\nnpx tsx server.ts\n# In another terminal, run a server in the VM and fetch it\nnpx tsx client-run-server.ts\nnpx tsx client-fetch.ts\n```\n\nExpect a `200` status and `Hello from inside the VM` printed by the fetch client.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/networking)\n","41bd8e2032d7c955",{"html":706,"metadata":707},"\u003Ch1 id=\"networking\">Networking\u003C/h1>\n\u003Cp>Run a service inside a VM and reach it — from the client and from the public web. Reach for this when an agent spins up a dev server, API, or web app that you need to call or share.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A process inside the VM binds a normal loopback port (e.g. \u003Ccode>3000\u003C/code>), exactly like any Node server. The client reaches it with \u003Ccode>agent.vmFetch(port, path, options)\u003C/code>, which proxies an HTTP request straight to that loopback port without exposing it to the network. To expose a port beyond loopback, set \u003Ccode>loopbackExemptPorts\u003C/code> on the VM config. For external sharing, \u003Ccode>agent.createSignedPreviewUrl(port, expiresInSeconds)\u003C/code> mints a short-lived signed URL; the \u003Ccode>preview\u003C/code> config sets default and maximum lifetimes, and old tokens fall off automatically as they expire.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\n# Start the VM host\nnpx tsx server.ts\n# In another terminal, run a server in the VM and fetch it\nnpx tsx client-run-server.ts\nnpx tsx client-fetch.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># Start the VM host\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># In another terminal, run a server in the VM and fetch it\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client-run-server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client-fetch.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\n# Start the VM host\nnpx tsx server.ts\n# In another terminal, run a server in the VM and fetch it\nnpx tsx client-run-server.ts\nnpx tsx client-fetch.ts\n\u003C/code>\u003C/pre>\n\u003Cp>Expect a \u003Ccode>200\u003C/code> status and \u003Ccode>Hello from inside the VM\u003C/code> printed by the fetch client.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/networking\">View source on GitHub\u003C/a>\u003C/p>",{"headings":708,"localImagePaths":714,"remoteImagePaths":715,"frontmatter":716},[709,711,712,713],{"depth":493,"slug":710,"text":373},"networking",{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/opencode",{"id":717,"data":719,"body":722,"digest":723,"rendered":724},{"title":720,"description":721},"OpenCode Agent","Run the OpenCode agent in a session using an Anthropic API key.","\nSpin up the OpenCode coding agent inside a VM session and prompt it with natural language. Reach for this when you want an autonomous coding agent that can read files, run commands, and follow project conventions — backed by your own Anthropic API key.\n\n## How it works\n\nRegister the `opencode` software with `agentOS({ software: [opencode] })` so the runtime knows the agent type. The client then calls `agent.createSession(\"opencode\", ...)`, passing `ANTHROPIC_API_KEY` through `env`, and drives the agent with `agent.sendPrompt`. The example also shows two extension points: drop a `SKILL.md` into `~/.config/opencode/skills/` before creating the session and the agent auto-discovers it, and wire in extra tools via `mcpServers` (local child-process or remote URL). Pre-install any `npx` MCP server first so install output does not corrupt the stdio handshake.\n\n## Run it\n\n```bash\nnpm install\nexport ANTHROPIC_API_KEY=sk-ant-...\nnpx tsx server.ts # starts the registry on http://localhost:6420\nnpx tsx client.ts # creates a session and prints the agent's reply\n```\n\nThe agent answers your prompt — e.g. listing the files in the working directory.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/opencode)\n","29e02aa02e906b38",{"html":725,"metadata":726},"\u003Cp>Spin up the OpenCode coding agent inside a VM session and prompt it with natural language. Reach for this when you want an autonomous coding agent that can read files, run commands, and follow project conventions — backed by your own Anthropic API key.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Register the \u003Ccode>opencode\u003C/code> software with \u003Ccode>agentOS({ software: [opencode] })\u003C/code> so the runtime knows the agent type. The client then calls \u003Ccode>agent.createSession(\"opencode\", ...)\u003C/code>, passing \u003Ccode>ANTHROPIC_API_KEY\u003C/code> through \u003Ccode>env\u003C/code>, and drives the agent with \u003Ccode>agent.sendPrompt\u003C/code>. The example also shows two extension points: drop a \u003Ccode>SKILL.md\u003C/code> into \u003Ccode>~/.config/opencode/skills/\u003C/code> before creating the session and the agent auto-discovers it, and wire in extra tools via \u003Ccode>mcpServers\u003C/code> (local child-process or remote URL). Pre-install any \u003Ccode>npx\u003C/code> MCP server first so install output does not corrupt the stdio handshake.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nexport ANTHROPIC_API_KEY=sk-ant-...\nnpx tsx server.ts # starts the registry on http://localhost:6420\nnpx tsx client.ts # creates a session and prints the agent's reply\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#FF8F40">export\u003C/span>\u003Cspan style="color:#BFBDB6"> ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#BFBDB6">sk-ant-...\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # starts the registry on http://localhost:6420\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # creates a session and prints the agent's reply\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nexport ANTHROPIC_API_KEY=sk-ant-...\nnpx tsx server.ts # starts the registry on http://localhost:6420\nnpx tsx client.ts # creates a session and prints the agent's reply\n\u003C/code>\u003C/pre>\n\u003Cp>The agent answers your prompt — e.g. listing the files in the working directory.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/opencode\">View source on GitHub\u003C/a>\u003C/p>",{"headings":727,"localImagePaths":731,"remoteImagePaths":732,"frontmatter":733},[728,729,730],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/permissions",{"id":734,"data":736,"body":738,"digest":739,"rendered":740},{"title":189,"description":737},"Apply permission policies: grant network, deny filesystem paths, and scope what the guest can do.","\nPermission policies decide what guest code is allowed to touch — the network, the filesystem, and named bindings. Reach for this when you need to hand untrusted or agent-generated code a VM that can only do exactly what you intend.\n\n## How it works\n\nEach policy is a small object passed to `agentOS({ permissions })`. A policy sets a `default` (`allow` or `deny`) and a list of `rules` that flip the decision for specific paths, hosts, or binding names. This example composes four policies and merges them into one permission set:\n\n- **Network** granted outright, with a stricter override that denies by default and allows only `api.example.com`.\n- **Filesystem** allowed by default but denied for anything under `/vault/**`.\n- **Bindings** denied by default, allowing only the `add` binding by name.\n\nRules are evaluated against the defaults, so you compose from broad posture down to narrow exceptions. The resulting VM enforces all of them on every guest operation.\n\n## Run it\n\n```sh\nnpm install\nnpx tsx server.ts\n```\n\nThe registry starts with a VM whose guest can reach `api.example.com`, cannot read `/vault`, and can only invoke the `add` binding.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/permissions)\n","dc539bf430153ff4",{"html":741,"metadata":742},"\u003Cp>Permission policies decide what guest code is allowed to touch — the network, the filesystem, and named bindings. Reach for this when you need to hand untrusted or agent-generated code a VM that can only do exactly what you intend.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>Each policy is a small object passed to \u003Ccode>agentOS({ permissions })\u003C/code>. A policy sets a \u003Ccode>default\u003C/code> (\u003Ccode>allow\u003C/code> or \u003Ccode>deny\u003C/code>) and a list of \u003Ccode>rules\u003C/code> that flip the decision for specific paths, hosts, or binding names. This example composes four policies and merges them into one permission set:\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Network\u003C/strong> granted outright, with a stricter override that denies by default and allows only \u003Ccode>api.example.com\u003C/code>.\u003C/li>\n\u003Cli>\u003Cstrong>Filesystem\u003C/strong> allowed by default but denied for anything under \u003Ccode>/vault/**\u003C/code>.\u003C/li>\n\u003Cli>\u003Cstrong>Bindings\u003C/strong> denied by default, allowing only the \u003Ccode>add\u003C/code> binding by name.\u003C/li>\n\u003C/ul>\n\u003Cp>Rules are evaluated against the defaults, so you compose from broad posture down to narrow exceptions. The resulting VM enforces all of them on every guest operation.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpx tsx server.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpx tsx server.ts\n\u003C/code>\u003C/pre>\n\u003Cp>The registry starts with a VM whose guest can reach \u003Ccode>api.example.com\u003C/code>, cannot read \u003Ccode>/vault\u003C/code>, and can only invoke the \u003Ccode>add\u003C/code> binding.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/permissions\">View source on GitHub\u003C/a>\u003C/p>",{"headings":743,"localImagePaths":747,"remoteImagePaths":748,"frontmatter":749},[744,745,746],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/persistence",{"id":750,"data":752,"body":755,"digest":756,"rendered":757},{"title":753,"description":754},"Persistence","Session persistence: lifecycle management and resuming a session after disconnect.","\nVMs sleep when idle and wake on demand, so sessions outlive any single connection. Reach for this when an agent needs to survive client disconnects, restarts, or long gaps between turns without losing its transcript.\n\n## How it works\n\nThe server registers a VM with `agentOS({ software: [pi] })` and `setup`. On the client, `connect()` surfaces `vmBooted` and `vmShutdown` lifecycle events — the shutdown payload's `reason` (`\"sleep\"`, `\"destroy\"`, or `\"error\"`) tells you why the VM stopped. Sessions are written to durable storage as they run, so even with no VM running you can call `vm.listPersistedSessions()` to enumerate past sessions and `vm.getSessionEvents(sessionId)` to replay a session's ordered event transcript after a disconnect.\n\n## Run it\n\n```sh\nnpm install\nnpx tsx examples/persistence/server.ts # terminal 1: start the registry\nnpx tsx examples/persistence/lifecycle-client.ts # terminal 2: watch boot/shutdown events\nnpx tsx examples/persistence/resume-client.ts # later: list and replay persisted sessions\n```\n\nThe lifecycle client logs `VM is ready` then shutdown reasons; the resume client prints prior session counts and replays the latest transcript.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/persistence)\n","3c8f8f5aa6d86cb5",{"html":758,"metadata":759},"\u003Cp>VMs sleep when idle and wake on demand, so sessions outlive any single connection. Reach for this when an agent needs to survive client disconnects, restarts, or long gaps between turns without losing its transcript.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The server registers a VM with \u003Ccode>agentOS({ software: [pi] })\u003C/code> and \u003Ccode>setup\u003C/code>. On the client, \u003Ccode>connect()\u003C/code> surfaces \u003Ccode>vmBooted\u003C/code> and \u003Ccode>vmShutdown\u003C/code> lifecycle events — the shutdown payload’s \u003Ccode>reason\u003C/code> (\u003Ccode>\"sleep\"\u003C/code>, \u003Ccode>\"destroy\"\u003C/code>, or \u003Ccode>\"error\"\u003C/code>) tells you why the VM stopped. Sessions are written to durable storage as they run, so even with no VM running you can call \u003Ccode>vm.listPersistedSessions()\u003C/code> to enumerate past sessions and \u003Ccode>vm.getSessionEvents(sessionId)\u003C/code> to replay a session’s ordered event transcript after a disconnect.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpx tsx examples/persistence/server.ts # terminal 1: start the registry\nnpx tsx examples/persistence/lifecycle-client.ts # terminal 2: watch boot/shutdown events\nnpx tsx examples/persistence/resume-client.ts # later: list and replay persisted sessions\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> examples/persistence/server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # terminal 1: start the registry\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> examples/persistence/lifecycle-client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # terminal 2: watch boot/shutdown events\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> examples/persistence/resume-client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # later: list and replay persisted sessions\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpx tsx examples/persistence/server.ts # terminal 1: start the registry\nnpx tsx examples/persistence/lifecycle-client.ts # terminal 2: watch boot/shutdown events\nnpx tsx examples/persistence/resume-client.ts # later: list and replay persisted sessions\n\u003C/code>\u003C/pre>\n\u003Cp>The lifecycle client logs \u003Ccode>VM is ready\u003C/code> then shutdown reasons; the resume client prints prior session counts and replays the latest transcript.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/persistence\">View source on GitHub\u003C/a>\u003C/p>",{"headings":760,"localImagePaths":764,"remoteImagePaths":765,"frontmatter":766},[761,762,763],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/pi",{"id":767,"data":769,"body":772,"digest":773,"rendered":774},{"title":770,"description":771},"Pi Agent","Run the Pi coding agent in a session, including quick start and session management.","\nSpin up the Pi coding agent inside a VM, open a session, and send it prompts. Reach for this when you want an end-to-end agent loop — quick start plus the session knobs for skills and MCP servers.\n\n## How it works\n\nThe server registers a VM with the `pi` software package and starts the registry. The client grabs a VM with `getOrCreate`, then `createSession(\"pi\", …)` passing the `ANTHROPIC_API_KEY` through `env`. From there `sendPrompt` runs a turn and returns the agent's `text`. Sessions are configurable: drop a `SKILL.md` into the agent's skills directory (via `mkdir` + `writeFile`) before creating the session and it's auto-discovered, and pass `mcpServers` (local child-process or remote URL) to expose extra tools. Pre-install any `npx`-launched MCP server so install output doesn't corrupt the stdio handshake.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # then run the client in another shell\n```\n\nThe agent answers the prompt and prints its response to the console.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/pi)\n","4695a9d363523cb5",{"html":775,"metadata":776},"\u003Cp>Spin up the Pi coding agent inside a VM, open a session, and send it prompts. Reach for this when you want an end-to-end agent loop — quick start plus the session knobs for skills and MCP servers.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The server registers a VM with the \u003Ccode>pi\u003C/code> software package and starts the registry. The client grabs a VM with \u003Ccode>getOrCreate\u003C/code>, then \u003Ccode>createSession(\"pi\", …)\u003C/code> passing the \u003Ccode>ANTHROPIC_API_KEY\u003C/code> through \u003Ccode>env\u003C/code>. From there \u003Ccode>sendPrompt\u003C/code> runs a turn and returns the agent’s \u003Ccode>text\u003C/code>. Sessions are configurable: drop a \u003Ccode>SKILL.md\u003C/code> into the agent’s skills directory (via \u003Ccode>mkdir\u003C/code> + \u003Ccode>writeFile\u003C/code>) before creating the session and it’s auto-discovered, and pass \u003Ccode>mcpServers\u003C/code> (local child-process or remote URL) to expose extra tools. Pre-install any \u003Ccode>npx\u003C/code>-launched MCP server so install output doesn’t corrupt the stdio handshake.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # then run the client in another shell\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # then run the client in another shell\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # then run the client in another shell\n\u003C/code>\u003C/pre>\n\u003Cp>The agent answers the prompt and prints its response to the console.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/pi\">View source on GitHub\u003C/a>\u003C/p>",{"headings":777,"localImagePaths":781,"remoteImagePaths":782,"frontmatter":783},[778,779,780],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/processes",{"id":784,"data":786,"body":788,"digest":789,"rendered":790},{"title":389,"description":787},"Process management inside the VM: exec, spawn, stdin, lifecycle, shell sessions, process events, and visibility.","\nRun commands and long-lived processes inside a VM, stream their output, and drive interactive shells. Reach for this whenever an agent needs to invoke tools, start a dev server, pipe data over stdin, or attach a terminal.\n\n## How it works\n\nA `server.ts` registers a VM with its software, and each script connects with `createClient` and grabs a VM via `client.vm.getOrCreate(\"my-agent\")`. From there the VM handle exposes the full process surface:\n\n- **`exec`** — run a command to completion and collect `stdout`, `stderr`, and `exitCode` in one call.\n- **`spawn` + lifecycle** — start a process for a `pid`, then `listProcesses`, `getProcess`, `waitProcess`, `stopProcess` (SIGTERM), and `killProcess` (SIGKILL).\n- **stdin** — `writeProcessStdin` and `closeProcessStdin` to feed a running process, including an interactive `sh` (see `shell.ts`).\n- **events** — `agent.connect()` returns a connection that emits `processOutput` / `processExit` and `shellData`, so output streams live as it is produced.\n\n`visibility.ts` shows how to enumerate and inspect everything running in the VM.\n\n## Run it\n\n```bash\nnpm install\nnpx tsx server.ts & # start the VM registry on :6420\nnpx tsx exec.ts # then run any of the scripts (spawn.ts, stdin.ts, shell.ts, ...)\n```\n\nEach script prints its process output and exit codes to the console.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/processes)\n","5d926edd519f9a13",{"html":791,"metadata":792},"\u003Cp>Run commands and long-lived processes inside a VM, stream their output, and drive interactive shells. Reach for this whenever an agent needs to invoke tools, start a dev server, pipe data over stdin, or attach a terminal.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A \u003Ccode>server.ts\u003C/code> registers a VM with its software, and each script connects with \u003Ccode>createClient\u003C/code> and grabs a VM via \u003Ccode>client.vm.getOrCreate(\"my-agent\")\u003C/code>. From there the VM handle exposes the full process surface:\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>\u003Ccode>exec\u003C/code>\u003C/strong> — run a command to completion and collect \u003Ccode>stdout\u003C/code>, \u003Ccode>stderr\u003C/code>, and \u003Ccode>exitCode\u003C/code> in one call.\u003C/li>\n\u003Cli>\u003Cstrong>\u003Ccode>spawn\u003C/code> + lifecycle\u003C/strong> — start a process for a \u003Ccode>pid\u003C/code>, then \u003Ccode>listProcesses\u003C/code>, \u003Ccode>getProcess\u003C/code>, \u003Ccode>waitProcess\u003C/code>, \u003Ccode>stopProcess\u003C/code> (SIGTERM), and \u003Ccode>killProcess\u003C/code> (SIGKILL).\u003C/li>\n\u003Cli>\u003Cstrong>stdin\u003C/strong> — \u003Ccode>writeProcessStdin\u003C/code> and \u003Ccode>closeProcessStdin\u003C/code> to feed a running process, including an interactive \u003Ccode>sh\u003C/code> (see \u003Ccode>shell.ts\u003C/code>).\u003C/li>\n\u003Cli>\u003Cstrong>events\u003C/strong> — \u003Ccode>agent.connect()\u003C/code> returns a connection that emits \u003Ccode>processOutput\u003C/code> / \u003Ccode>processExit\u003C/code> and \u003Ccode>shellData\u003C/code>, so output streams live as it is produced.\u003C/li>\n\u003C/ul>\n\u003Cp>\u003Ccode>visibility.ts\u003C/code> shows how to enumerate and inspect everything running in the VM.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nnpx tsx server.ts & # start the VM registry on :6420\nnpx tsx exec.ts # then run any of the scripts (spawn.ts, stdin.ts, shell.ts, ...)\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#BFBDB6B3"> &#x26;\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the VM registry on :6420\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> exec.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # then run any of the scripts (spawn.ts, stdin.ts, shell.ts, ...)\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nnpx tsx server.ts & # start the VM registry on :6420\nnpx tsx exec.ts # then run any of the scripts (spawn.ts, stdin.ts, shell.ts, ...)\n\u003C/code>\u003C/pre>\n\u003Cp>Each script prints its process output and exit codes to the console.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/processes\">View source on GitHub\u003C/a>\u003C/p>",{"headings":793,"localImagePaths":797,"remoteImagePaths":798,"frontmatter":799},[794,795,796],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/queues",{"id":800,"data":802,"body":805,"digest":806,"rendered":807},{"title":803,"description":804},"Queues","Process agent tasks one at a time through a RivetKit queue, with ingest and review pipelines.","\nRun agent work through a durable queue so tasks are handled one at a time instead of all at once. Reach for this when prompts arrive faster than agents should process them — webhook bursts, batch jobs, or any workload where serialized, back-pressured execution beats parallel chaos.\n\n## How it works\n\nA RivetKit `actor` declares a `queue` and drains it inside its `run` loop with `c.queue.iter()`, processing each message sequentially. For every message the actor opens an Agent OS session against a shared VM, sends the prompt, and closes the session — so only one task runs at a time per actor.\n\nThe example shows three patterns over the same primitive:\n\n- **Basic** (`server.ts` / `client.ts`) — clients `send` prompts onto the queue; the runner processes them in order.\n- **Ingest** (`ingest-server.ts` / `ingest-client.ts`) — an HTTP action `push`es webhook payloads onto the queue for decoupled intake.\n- **Review** (`review-server.ts` / `review-client.ts`) — a completable queue (`iter({ completable: true })`) where the client `send`s with `{ wait: true }` and blocks for the agent's returned summary.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # in one terminal\nnpx tsx client.ts # in another\n```\n\nTasks queue up and the agent works through them one at a time; swap in `ingest-*` or `review-*` to try the other pipelines.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/queues)\n","d8d2a105c20fc691",{"html":808,"metadata":809},"\u003Cp>Run agent work through a durable queue so tasks are handled one at a time instead of all at once. Reach for this when prompts arrive faster than agents should process them — webhook bursts, batch jobs, or any workload where serialized, back-pressured execution beats parallel chaos.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A RivetKit \u003Ccode>actor\u003C/code> declares a \u003Ccode>queue\u003C/code> and drains it inside its \u003Ccode>run\u003C/code> loop with \u003Ccode>c.queue.iter()\u003C/code>, processing each message sequentially. For every message the actor opens an Agent OS session against a shared VM, sends the prompt, and closes the session — so only one task runs at a time per actor.\u003C/p>\n\u003Cp>The example shows three patterns over the same primitive:\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Basic\u003C/strong> (\u003Ccode>server.ts\u003C/code> / \u003Ccode>client.ts\u003C/code>) — clients \u003Ccode>send\u003C/code> prompts onto the queue; the runner processes them in order.\u003C/li>\n\u003Cli>\u003Cstrong>Ingest\u003C/strong> (\u003Ccode>ingest-server.ts\u003C/code> / \u003Ccode>ingest-client.ts\u003C/code>) — an HTTP action \u003Ccode>push\u003C/code>es webhook payloads onto the queue for decoupled intake.\u003C/li>\n\u003Cli>\u003Cstrong>Review\u003C/strong> (\u003Ccode>review-server.ts\u003C/code> / \u003Ccode>review-client.ts\u003C/code>) — a completable queue (\u003Ccode>iter({ completable: true })\u003C/code>) where the client \u003Ccode>send\u003C/code>s with \u003Ccode>{ wait: true }\u003C/code> and blocks for the agent’s returned summary.\u003C/li>\n\u003C/ul>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # in one terminal\nnpx tsx client.ts # in another\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in one terminal\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in another\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # in one terminal\nnpx tsx client.ts # in another\n\u003C/code>\u003C/pre>\n\u003Cp>Tasks queue up and the agent works through them one at a time; swap in \u003Ccode>ingest-*\u003C/code> or \u003Ccode>review-*\u003C/code> to try the other pipelines.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/queues\">View source on GitHub\u003C/a>\u003C/p>",{"headings":810,"localImagePaths":814,"remoteImagePaths":815,"frontmatter":816},[811,812,813],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/quickstart-app",{"id":817,"data":819,"body":822,"digest":823,"rendered":824},{"title":820,"description":821},"Quickstart App","Full RivetKit app: an agentOS server registry plus a client that streams session events.","\nA complete starting point that wires an agentOS server to a client. Reach for this when you want the whole loop in one place: a server that registers a VM with agent software, and a client that opens a session, sends a prompt, and streams the agent's events back.\n\n## How it works\n\n`server.ts` builds a VM with `agentOS({ software: [pi] })`, registers it via `setup`, and starts the RivetKit registry. The client connects to that registry, calls `getOrCreate` to obtain a VM handle, and subscribes to `sessionEvent` over a live connection. It then creates a `pi` session (passing the Anthropic API key through `env`), sends a prompt, and reads back the file the agent wrote to `/workspace`. An `Agent.tsx` component shows the same flow from React, streaming events into component state with `useEvent`.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the registry\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # in another shell, drive a session\n```\n\nThe client prints streamed session events and the contents of the `hello.js` file the agent creates.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/quickstart-app)\n","1a00ecfd763c3dec",{"html":825,"metadata":826},"\u003Cp>A complete starting point that wires an agentOS server to a client. Reach for this when you want the whole loop in one place: a server that registers a VM with agent software, and a client that opens a session, sends a prompt, and streams the agent’s events back.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>\u003Ccode>server.ts\u003C/code> builds a VM with \u003Ccode>agentOS({ software: [pi] })\u003C/code>, registers it via \u003Ccode>setup\u003C/code>, and starts the RivetKit registry. The client connects to that registry, calls \u003Ccode>getOrCreate\u003C/code> to obtain a VM handle, and subscribes to \u003Ccode>sessionEvent\u003C/code> over a live connection. It then creates a \u003Ccode>pi\u003C/code> session (passing the Anthropic API key through \u003Ccode>env\u003C/code>), sends a prompt, and reads back the file the agent wrote to \u003Ccode>/workspace\u003C/code>. An \u003Ccode>Agent.tsx\u003C/code> component shows the same flow from React, streaming events into component state with \u003Ccode>useEvent\u003C/code>.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the registry\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # in another shell, drive a session\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the registry\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # in another shell, drive a session\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the registry\nANTHROPIC_API_KEY=sk-... npx tsx client.ts # in another shell, drive a session\n\u003C/code>\u003C/pre>\n\u003Cp>The client prints streamed session events and the contents of the \u003Ccode>hello.js\u003C/code> file the agent creates.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/quickstart-app\">View source on GitHub\u003C/a>\u003C/p>",{"headings":827,"localImagePaths":831,"remoteImagePaths":832,"frontmatter":833},[828,829,830],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/resource-limits",{"id":834,"data":836,"body":838,"digest":839,"rendered":840},{"title":229,"description":837},"Configure VM resource limits, JavaScript CPU/wall-clock budgets, Python caps, and WASM runtime limits.","\nCap how much of the host a VM can consume. Reach for this when you run untrusted or agent-generated code and need hard ceilings on processes, file descriptors, sockets, filesystem storage, JavaScript CPU time, Python execution, and WASM runtime work.\n\n## How it works\n\nThe VM accepts a typed `limits` block when you call `agentOS({ ... })`. Kernel resources live under `limits.resources`; JavaScript, Python, and WASM runtime limits live under `limits.jsRuntime`, `limits.python`, and `limits.wasm`. The sidecar forwards these over the VM creation wire, so guest env vars cannot raise or override its own caps.\n\n## Run it\n\n```sh\nnpm install\nnpx tsx server.ts\n```\n\nThis starts a registry whose VM is provisioned with the configured resource caps.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/resource-limits)\n","4e3e2f27b61d5b6a",{"html":841,"metadata":842},"\u003Cp>Cap how much of the host a VM can consume. Reach for this when you run untrusted or agent-generated code and need hard ceilings on processes, file descriptors, sockets, filesystem storage, JavaScript CPU time, Python execution, and WASM runtime work.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The VM accepts a typed \u003Ccode>limits\u003C/code> block when you call \u003Ccode>agentOS({ ... })\u003C/code>. Kernel resources live under \u003Ccode>limits.resources\u003C/code>; JavaScript, Python, and WASM runtime limits live under \u003Ccode>limits.jsRuntime\u003C/code>, \u003Ccode>limits.python\u003C/code>, and \u003Ccode>limits.wasm\u003C/code>. The sidecar forwards these over the VM creation wire, so guest env vars cannot raise or override its own caps.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpx tsx server.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpx tsx server.ts\n\u003C/code>\u003C/pre>\n\u003Cp>This starts a registry whose VM is provisioned with the configured resource caps.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/resource-limits\">View source on GitHub\u003C/a>\u003C/p>",{"headings":843,"localImagePaths":847,"remoteImagePaths":848,"frontmatter":849},[844,845,846],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/sandbox",{"id":850,"data":852,"body":855,"digest":856,"rendered":857},{"title":853,"description":854},"Sandbox","Mount a Sandbox Agent (Docker) filesystem into the VM and expose its process management as bindings.","\nBack a VM with a real Sandbox Agent container: the sandbox's filesystem appears as a mount inside the VM, and its process management is callable as bindings. Reach for this when you want guest code to read, write, and run against a live Docker sandbox instead of the in-memory VFS.\n\n## How it works\n\nThe server starts a sandbox through `SandboxAgent.start({ sandbox: docker() })`, then wires it into `agentOS` two ways. `createSandboxFs({ client })` returns a mount-plugin descriptor that projects the sandbox filesystem under `/home/agentos/sandbox`, so `vm.writeFile` and `vm.exec` operate on real container files. `createSandboxBindings({ client })` exposes the sandbox's process management as bindings, surfaced inside the VM as the `agentos-sandbox` CLI command. From the client you write a file to the mount, `exec` it, invoke a binding like `run-command`, and `spawn` a long-running process whose stdout/stderr stream back over `vm.connect()`.\n\n## Run it\n\n```sh\nnpm install\nnpm run server # starts the VM with the sandbox mount + bindings\nnpm run client # writes a file, runs it, and streams process output\n```\n\nYou should see `hello` printed from a file executed inside the Docker sandbox, followed by streamed output from the spawned dev process.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/sandbox)\n","4c5dab6eacfc04d1",{"html":858,"metadata":859},"\u003Cp>Back a VM with a real Sandbox Agent container: the sandbox’s filesystem appears as a mount inside the VM, and its process management is callable as bindings. Reach for this when you want guest code to read, write, and run against a live Docker sandbox instead of the in-memory VFS.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The server starts a sandbox through \u003Ccode>SandboxAgent.start({ sandbox: docker() })\u003C/code>, then wires it into \u003Ccode>agentOS\u003C/code> two ways. \u003Ccode>createSandboxFs({ client })\u003C/code> returns a mount-plugin descriptor that projects the sandbox filesystem under \u003Ccode>/home/agentos/sandbox\u003C/code>, so \u003Ccode>vm.writeFile\u003C/code> and \u003Ccode>vm.exec\u003C/code> operate on real container files. \u003Ccode>createSandboxBindings({ client })\u003C/code> exposes the sandbox’s process management as bindings, surfaced inside the VM as the \u003Ccode>agentos-sandbox\u003C/code> CLI command. From the client you write a file to the mount, \u003Ccode>exec\u003C/code> it, invoke a binding like \u003Ccode>run-command\u003C/code>, and \u003Ccode>spawn\u003C/code> a long-running process whose stdout/stderr stream back over \u003Ccode>vm.connect()\u003C/code>.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpm run server # starts the VM with the sandbox mount + bindings\nnpm run client # writes a file, runs it, and streams process output\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> run\u003C/span>\u003Cspan style="color:#AAD94C"> server\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # starts the VM with the sandbox mount + bindings\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> run\u003C/span>\u003Cspan style="color:#AAD94C"> client\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # writes a file, runs it, and streams process output\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpm run server # starts the VM with the sandbox mount + bindings\nnpm run client # writes a file, runs it, and streams process output\n\u003C/code>\u003C/pre>\n\u003Cp>You should see \u003Ccode>hello\u003C/code> printed from a file executed inside the Docker sandbox, followed by streamed output from the spawned dev process.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/sandbox\">View source on GitHub\u003C/a>\u003C/p>",{"headings":860,"localImagePaths":864,"remoteImagePaths":865,"frontmatter":866},[861,862,863],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/sessions",{"id":867,"data":869,"body":871,"digest":872,"rendered":873},{"title":253,"description":870},"Create, manage, and stream agent sessions over the RivetKit actor client.","\nSpin up an agent VM, open sessions against it, and drive them end to end: send prompts, stream responses, switch models, replay history, and tear sessions down. Reach for this when you need full lifecycle control over an agent rather than a one-shot prompt.\n\n## How it works\n\nThe server registers an agent VM with `agentOS({ software: [pi] })` and exposes it through a typed RivetKit `setup` registry. The client connects with `createClient` and grabs a VM handle with `getOrCreate`. From that handle you call `createSession` (with options like `env`, `cwd`, `mcpServers`, and `additionalInstructions`), then `sendPrompt`/`cancelPrompt` to run work. A `connect()` connection surfaces `sessionEvent`, `vmBooted`, and `vmShutdown` events for live streaming — subscribe before triggering actions so nothing is missed. Runtime knobs (`setModel`, `setMode`, `setThoughtLevel`), event replay (`getSessionEvents`, `getSequencedEvents`), persisted history, and multi-session fan-out within one VM round out the surface.\n\n## Run it\n\n```bash\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the registry\n# in another shell: drive the client functions\nANTHROPIC_API_KEY=sk-... npx tsx client.ts\n```\n\nThe server boots the agent VM and the client opens sessions, streams events, and prints session IDs and responses.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/sessions)\n","bd355ad1b6089ca5",{"html":874,"metadata":875},"\u003Cp>Spin up an agent VM, open sessions against it, and drive them end to end: send prompts, stream responses, switch models, replay history, and tear sessions down. Reach for this when you need full lifecycle control over an agent rather than a one-shot prompt.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>The server registers an agent VM with \u003Ccode>agentOS({ software: [pi] })\u003C/code> and exposes it through a typed RivetKit \u003Ccode>setup\u003C/code> registry. The client connects with \u003Ccode>createClient\u003C/code> and grabs a VM handle with \u003Ccode>getOrCreate\u003C/code>. From that handle you call \u003Ccode>createSession\u003C/code> (with options like \u003Ccode>env\u003C/code>, \u003Ccode>cwd\u003C/code>, \u003Ccode>mcpServers\u003C/code>, and \u003Ccode>additionalInstructions\u003C/code>), then \u003Ccode>sendPrompt\u003C/code>/\u003Ccode>cancelPrompt\u003C/code> to run work. A \u003Ccode>connect()\u003C/code> connection surfaces \u003Ccode>sessionEvent\u003C/code>, \u003Ccode>vmBooted\u003C/code>, and \u003Ccode>vmShutdown\u003C/code> events for live streaming — subscribe before triggering actions so nothing is missed. Runtime knobs (\u003Ccode>setModel\u003C/code>, \u003Ccode>setMode\u003C/code>, \u003Ccode>setThoughtLevel\u003C/code>), event replay (\u003Ccode>getSessionEvents\u003C/code>, \u003Ccode>getSequencedEvents\u003C/code>), persisted history, and multi-session fan-out within one VM round out the surface.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the registry\n# in another shell: drive the client functions\nANTHROPIC_API_KEY=sk-... npx tsx client.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the registry\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#5A6673;font-style:italic"># in another shell: drive the client functions\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the registry\n# in another shell: drive the client functions\nANTHROPIC_API_KEY=sk-... npx tsx client.ts\n\u003C/code>\u003C/pre>\n\u003Cp>The server boots the agent VM and the client opens sessions, streams events, and prints session IDs and responses.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/sessions\">View source on GitHub\u003C/a>\u003C/p>",{"headings":876,"localImagePaths":880,"remoteImagePaths":881,"frontmatter":882},[877,878,879],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/software",{"id":883,"data":885,"body":887,"digest":888,"rendered":889},{"title":261,"description":886},"Declare which software packages and CLI commands are available inside the VM.","\nThe commands an agent can run are determined by the software you install into its VM. This example declares a software set so a shell pipeline like `echo hello | grep hello` resolves inside the sandbox.\n\n## How it works\n\n`agentOS({ software: [...] })` takes a list of imported software packages, and together they define the CLI surface available to the guest. Common utilities — coreutils, sed, grep, gawk, findutils, diffutils, tar, and gzip — ship by default, so you only list the extras you need; here `pi` adds the agent itself. The client then runs commands through the VM via `exec`, which only succeed when the underlying binaries are present in the declared software set.\n\n## Run it\n\n```sh\nnpm install\nnpm run server # starts the registry on http://localhost:6420\nnpm run client # runs \"echo hello | grep hello\" in the VM, prints \"hello\"\n```\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/software)\n","3ab6bf586094ee8b",{"html":890,"metadata":891},"\u003Cp>The commands an agent can run are determined by the software you install into its VM. This example declares a software set so a shell pipeline like \u003Ccode>echo hello | grep hello\u003C/code> resolves inside the sandbox.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>\u003Ccode>agentOS({ software: [...] })\u003C/code> takes a list of imported software packages, and together they define the CLI surface available to the guest. Common utilities — coreutils, sed, grep, gawk, findutils, diffutils, tar, and gzip — ship by default, so you only list the extras you need; here \u003Ccode>pi\u003C/code> adds the agent itself. The client then runs commands through the VM via \u003Ccode>exec\u003C/code>, which only succeed when the underlying binaries are present in the declared software set.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nnpm run server # starts the registry on http://localhost:6420\nnpm run client # runs "echo hello | grep hello" in the VM, prints "hello"\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> run\u003C/span>\u003Cspan style="color:#AAD94C"> server\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # starts the registry on http://localhost:6420\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> run\u003C/span>\u003Cspan style="color:#AAD94C"> client\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # runs "echo hello | grep hello" in the VM, prints "hello"\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nnpm run server # starts the registry on http://localhost:6420\nnpm run client # runs \"echo hello | grep hello\" in the VM, prints \"hello\"\n\u003C/code>\u003C/pre>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/software\">View source on GitHub\u003C/a>\u003C/p>",{"headings":892,"localImagePaths":896,"remoteImagePaths":897,"frontmatter":898},[893,894,895],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/webhooks",{"id":899,"data":901,"body":903,"digest":904,"rendered":905},{"title":285,"description":902},"Receive inbound webhooks (e.g. Slack) over Hono and dispatch them to an agent through a queue.","\nWire an external service's webhooks into an agent. Reach for this when a third party (Slack, GitHub, Stripe) POSTs events to you and you want an agent to react — without blocking the webhook response on the agent's work.\n\n## How it works\n\nA small [Hono](https://hono.dev) server exposes a `/slack/events` endpoint that handles Slack's URL verification handshake and then enqueues each inbound message onto a RivetKit `queue`. A `slackWorker` actor drains that queue, and for every message it spins up an Agent OS session, prompts the agent with the message text, and posts the reply back to Slack via the chat API. Decoupling the HTTP handler from the worker keeps webhook responses fast and lets agent runs proceed asynchronously.\n\n## Run it\n\n```sh\nnpm install\nANTHROPIC_API_KEY=sk-... SLACK_BOT_TOKEN=xoxb-... npx tsx server.ts\n```\n\nThe server listens for Slack events; each incoming message is queued, answered by the agent, and replied to in-channel.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/webhooks)\n","3a782c0e6033e24d",{"html":906,"metadata":907},"\u003Cp>Wire an external service’s webhooks into an agent. Reach for this when a third party (Slack, GitHub, Stripe) POSTs events to you and you want an agent to react — without blocking the webhook response on the agent’s work.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A small \u003Ca href=\"https://hono.dev\">Hono\u003C/a> server exposes a \u003Ccode>/slack/events\u003C/code> endpoint that handles Slack’s URL verification handshake and then enqueues each inbound message onto a RivetKit \u003Ccode>queue\u003C/code>. A \u003Ccode>slackWorker\u003C/code> actor drains that queue, and for every message it spins up an Agent OS session, prompts the agent with the message text, and posts the reply back to Slack via the chat API. Decoupling the HTTP handler from the worker keeps webhook responses fast and lets agent runs proceed asynchronously.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"sh\" code=\"npm install\nANTHROPIC_API_KEY=sk-... SLACK_BOT_TOKEN=xoxb-... npx tsx server.ts\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#BFBDB6"> SLACK_BOT_TOKEN\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">xoxb-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-sh\">npm install\nANTHROPIC_API_KEY=sk-... SLACK_BOT_TOKEN=xoxb-... npx tsx server.ts\n\u003C/code>\u003C/pre>\n\u003Cp>The server listens for Slack events; each incoming message is queued, answered by the agent, and replied to in-channel.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/webhooks\">View source on GitHub\u003C/a>\u003C/p>",{"headings":908,"localImagePaths":912,"remoteImagePaths":913,"frontmatter":914},[909,910,911],{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks/workflows",{"id":915,"data":917,"body":920,"digest":921,"rendered":922},{"title":918,"description":919},"Workflows","Durable multi-step workflows that drive a VM across restarts, chaining each step's output into the next.","\n# Workflows\n\nRun multi-step agent work that survives crashes and restarts. Reach for this when a task has distinct stages — clone, fix, test, record — and you want each stage to be durable, retryable, and resumable rather than a single fragile call.\n\n## How it works\n\nA RivetKit `actor` whose `run` handler is built with `workflow()` orchestrates the steps, while a separate `agentOS` VM actor does the actual work over the client. Each `ctx.step(...)` is recorded, retried, and resumed independently: if the process crashes mid-run, replay skips completed steps and continues from where it left off. The orchestrator loops on a durable `queue`, waiting for the next request, then runs its steps in order against the VM. Output flows step-to-step through return values and the VM filesystem — the bug-fixer chains clone -> fix -> test -> record, and the code-reviewer writes a review file in one agent session and feeds it into a second. Sessions are created and closed inside a step, so they never outlive the work they back.\n\n## Run it\n\n```bash\nnpm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the orchestrator + VM\nnpx tsx client.ts # trigger the durable bug-fix workflow\n```\n\nThe client sends a request to the workflow queue; the workflow drives the VM through each step and prints the last issue and test exit code.\n\n## Source\n\n[View source on GitHub](https://github.com/rivet-dev/agentos/tree/main/examples/workflows)\n","a1a2e758a2472825",{"html":923,"metadata":924},"\u003Ch1 id=\"workflows\">Workflows\u003C/h1>\n\u003Cp>Run multi-step agent work that survives crashes and restarts. Reach for this when a task has distinct stages — clone, fix, test, record — and you want each stage to be durable, retryable, and resumable rather than a single fragile call.\u003C/p>\n\u003Ch2 id=\"how-it-works\">How it works\u003C/h2>\n\u003Cp>A RivetKit \u003Ccode>actor\u003C/code> whose \u003Ccode>run\u003C/code> handler is built with \u003Ccode>workflow()\u003C/code> orchestrates the steps, while a separate \u003Ccode>agentOS\u003C/code> VM actor does the actual work over the client. Each \u003Ccode>ctx.step(...)\u003C/code> is recorded, retried, and resumed independently: if the process crashes mid-run, replay skips completed steps and continues from where it left off. The orchestrator loops on a durable \u003Ccode>queue\u003C/code>, waiting for the next request, then runs its steps in order against the VM. Output flows step-to-step through return values and the VM filesystem — the bug-fixer chains clone -> fix -> test -> record, and the code-reviewer writes a review file in one agent session and feeds it into a second. Sessions are created and closed inside a step, so they never outlive the work they back.\u003C/p>\n\u003Ch2 id=\"run-it\">Run it\u003C/h2>\n\u003Cpre language=\"bash\" code=\"npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the orchestrator + VM\nnpx tsx client.ts # trigger the durable bug-fix workflow\n\" highlightedCode=\"\u003Cpre class="shiki ayu-dark" style="background-color:#0d1017;color:#bfbdb6" tabindex="0">\u003Ccode>\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npm\u003C/span>\u003Cspan style="color:#AAD94C"> install\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#BFBDB6">ANTHROPIC_API_KEY\u003C/span>\u003Cspan style="color:#F29668">=\u003C/span>\u003Cspan style="color:#AAD94C">sk-...\u003C/span>\u003Cspan style="color:#59C2FF"> npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> server.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # start the orchestrator + VM\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003Cspan style="color:#59C2FF">npx\u003C/span>\u003Cspan style="color:#AAD94C"> tsx\u003C/span>\u003Cspan style="color:#AAD94C"> client.ts\u003C/span>\u003Cspan style="color:#5A6673;font-style:italic"> # trigger the durable bug-fix workflow\u003C/span>\u003C/span>\n\u003Cspan class="line">\u003C/span>\u003C/code>\u003C/pre>\">\u003Ccode class=\"language-bash\">npm install\nANTHROPIC_API_KEY=sk-... npx tsx server.ts # start the orchestrator + VM\nnpx tsx client.ts # trigger the durable bug-fix workflow\n\u003C/code>\u003C/pre>\n\u003Cp>The client sends a request to the workflow queue; the workflow drives the VM through each step and prints the last issue and test exit code.\u003C/p>\n\u003Ch2 id=\"source\">Source\u003C/h2>\n\u003Cp>\u003Ca href=\"https://github.com/rivet-dev/agentos/tree/main/examples/workflows\">View source on GitHub\u003C/a>\u003C/p>",{"headings":925,"localImagePaths":931,"remoteImagePaths":932,"frontmatter":933},[926,928,929,930],{"depth":493,"slug":927,"text":918},"workflows",{"depth":454,"slug":455,"text":456},{"depth":454,"slug":458,"text":459},{"depth":454,"slug":461,"text":462},[],[],{},"cookbooks",{"id":934,"data":936,"body":939,"digest":940,"rendered":941},{"title":937,"description":938},"Cookbooks","Runnable agentOS examples.","agentOS cookbooks — runnable examples for every capability. Each page mirrors an example in the repo; follow the **View source on GitHub** link to run it.","4d08c63bfbca701b",{"html":942,"metadata":943},"\u003Cp>agentOS cookbooks — runnable examples for every capability. Each page mirrors an example in the repo; follow the \u003Cstrong>View source on GitHub\u003C/strong> link to run it.\u003C/p>",{"headings":944,"localImagePaths":945,"remoteImagePaths":946,"frontmatter":947},[],[],[],{}] \ No newline at end of file diff --git a/website/public/docs/docs/agents/custom.md b/website/public/docs/docs/agents/custom.md index 331472fd45..71f3c441c8 100644 --- a/website/public/docs/docs/agents/custom.md +++ b/website/public/docs/docs/agents/custom.md @@ -46,7 +46,7 @@ Register the package on the server with `software`. Sessions are then created fr import { agentOS, setup, defineSoftware } from "@rivet-dev/agentos"; const myAgent = defineSoftware({ - packageDir, // the packaged agent directory; its agentos-package.json carries the agent block + packagePath, // the packed agent .aospkg; its embedded manifest carries the agent block }); const vm = agentOS({ software: [myAgent] }); diff --git a/website/public/docs/docs/browser.md b/website/public/docs/docs/browser.md index c6ee2e56a5..2b97baa607 100644 --- a/website/public/docs/docs/browser.md +++ b/website/public/docs/docs/browser.md @@ -36,6 +36,4 @@ browse cloud sessions list # list cloud browser sessions browse cloud projects list # list Browserbase projects ``` -## Interactive browsing - -`browse` also has an [interactive driver mode](https://docs.browserbase.com/integrations/skills/browse-cli) (`browse open`, `browse click`, `browse fill`, …) that keeps a daemon running between commands. For interactive automation, run `browse` (or Playwright/Puppeteer) inside a sandbox via [Sandbox Mounting](/docs/sandbox). \ No newline at end of file +The [interactive driver mode](https://docs.browserbase.com/integrations/skills/browse-cli) (`browse open`, `browse click`, …) is not supported inside the VM yet ([#1631](https://github.com/rivet-dev/agentos/issues/1631)). For interactive automation, run `browse` inside a sandbox via [Sandbox Mounting](/docs/sandbox). \ No newline at end of file diff --git a/website/public/docs/docs/core.md b/website/public/docs/docs/core.md index 13ca655585..367240eb04 100644 --- a/website/public/docs/docs/core.md +++ b/website/public/docs/docs/core.md @@ -29,9 +29,7 @@ npm install @rivet-dev/agentos-core ## Boot a VM -Define the actor on the server: - -Then drive it from a typed client: +Create a VM and drive it directly — no actor runtime, no client/server split. `AgentOs.create()` boots the VM in-process and returns a handle you call directly: ## Sidecar process @@ -45,19 +43,21 @@ For advanced cases the core package exposes explicit sidecar handles so you can ## Processes -Long-running process output is delivered over the live `processOutput` / `processExit` events on a connection rather than per-pid callbacks: +Long-running process output is delivered through the `onStdout`/`onStderr` callbacks you pass to `spawn`, and exit through `onProcessExit(pid, …)`: ## Agent sessions -`createSession` returns a session record. All session operations take its `sessionId`. Session events and permission requests are delivered over the live connection (`sessionEvent` / `permissionRequest`): +`createSession` resolves to a session record; all session operations take its `sessionId`. Session events and permission requests are delivered through per-session callbacks (`onSessionEvent` / `onPermissionRequest`): -Subscribe to `sessionEvent` before sending a prompt so you do not miss the live stream. Persisted history can be read back later with `getSessionEvents()`. +Register `onSessionEvent` right after `createSession` so you do not miss the live stream — core session events are live-only and are not replayed. ## Networking +`fetch(port, request)` reaches a server running inside the VM: + ## Cron jobs -Cron jobs run an `"exec"` command or a `"session"` prompt on a schedule. Fired jobs are surfaced over the live `cronEvent` connection: +Cron jobs run an `"exec"` command or a `"session"` prompt on a schedule. Fired jobs are surfaced through the `onCronEvent` callback: ## Mounts @@ -66,21 +66,16 @@ Configure filesystem backends at boot time. Native mount plugins (host directories, S3, etc.) are passed via `plugin`, each identified by an `id` and a `config` object. -## `agentOS()` configuration reference - -When you use the [`agentOS()` actor](/docs/quickstart), all VM configuration is passed to the factory as a single flat object. This is the consolidated config block to copy and adapt: +## Configuration reference -The top-level fields are documented inline above. See [Mounts](#mounts), [Software](/docs/software), and (for the hooks) [Approvals](/docs/approvals). +All VM configuration is passed to `AgentOs.create()` as a single flat object. This is the consolidated config block to copy and adapt. The [`agentOS()` actor](/docs/quickstart) accepts the same options and layers persistence, sleep/wake, and preview URLs on top: -### Lifecycle hooks +The top-level fields are documented inline above. See [Mounts](#mounts) and [Software](/docs/software). -`onPermissionRequest(sessionId, request)` fires when an agent requests permission. `onSessionEvent(sessionId, event)` is a server-side hook called once for every session event: unlike the client-side `sessionEvent` connection subscription, it runs in the actor for every event regardless of connected clients, making it the place for server-side logging, persistence, or side effects. +### Session events -### Timeouts +With the core package, session events and permission requests are observed per-session on the `AgentOs` instance. `onSessionEvent(sessionId, event)` fires once for every session event; `onPermissionRequest(sessionId, request)` fires when an agent requests permission. Both are live-only callbacks — register them right after `createSession`: -| Setting | Default | Description | -|---------|---------|-------------| -| Action timeout | 15 minutes | Maximum time for any single action | -| Sleep grace period | 15 minutes | Time before sleeping after all activity stops | +### Timeouts and sleep -These are set internally by the `agentOS()` factory and cannot be overridden per-call. See [Persistence & Sleep](/docs/persistence) for details on the sleep lifecycle. \ No newline at end of file +Action timeouts and automatic sleep/wake are features of the [`agentOS()` actor](/docs/quickstart), not the core package. A core VM stays alive until you call `dispose()`. See [Persistence & Sleep](/docs/persistence) for the actor's sleep lifecycle. \ No newline at end of file diff --git a/website/public/docs/docs/custom-software/building-wasm.md b/website/public/docs/docs/custom-software/building-wasm.md index 6bff61cf32..a438d40101 100644 --- a/website/public/docs/docs/custom-software/building-wasm.md +++ b/website/public/docs/docs/custom-software/building-wasm.md @@ -10,45 +10,33 @@ You only need this to author new commands. To use existing ones, install the pub Command source and packages live under `registry/` in [secure-exec](https://github.com/rivet-dev/secure-exec/tree/main/registry): -- **`registry/native/crates/`**: the Rust source for the WASM commands. -- **`registry/native/c/`**: the C source for the WASM commands. -- **`registry/software//`**: the npm package for each command set (`@agentos-software/`). It exports a `{ packageDir }` descriptor pointing at the self-contained package directory (`package.json` + a `bin/` of compiled `.wasm` binaries + a generated `agentos-package.json`). +- **`registry/native/crates/commands//`**: the Rust source for each command — a cargo package named `cmd-` that emits a `` binary. +- **`registry/native/c/`**: the C source for the C-built commands. +- **`registry/software//`**: the npm package for each command set (`@agentos-software/`). It exports a `{ packagePath }` descriptor pointing at the packed `dist/package.aospkg`, and declares which binaries it ships in its `agentos-package.json` (`commands`, plus optional `aliases` and `stubs`). ## Build -Build everything from `registry/`: +Everything runs through `just` recipes at the secure-exec repo root: ```bash -make build # build all WASM binaries + the TypeScript packages -make copy-wasm # copy binaries into each package's wasm/ directory -make test +just registry-native # compile ALL native wasm binaries (slow; once per checkout) +just registry-native-cmd sh # recompile ONE command (cargo package cmd-sh) +just registry-build # stage + assemble every registry package +just registry-build ripgrep # ... or just one +just registry-status # per-package state; --remote adds npm dist-tags ``` -`copy-wasm` maps each compiled command into its `registry/software//` package, which the assembled `{ packageDir }` package then exposes on `$PATH`. The two toolchains build independently: +The native build compiles each command for `wasm32-wasip1` with the pinned **nightly** toolchain from `rust-toolchain.toml` (the build vendors and patches `std` for WASI), optimizes with `wasm-opt`, and drops the binaries in `registry/native/target/wasm32-wasip1/release/commands/`. C-based commands (e.g. `sqlite3`, `unzip`, `wget`, `zip`) compile with a **wasi-sdk** clang toolchain via `make -C registry/native/c`. -### Rust - -Most commands are Rust. The source lives in `registry/native/crates/` and compiles for `wasm32-wasip1` with the pinned **nightly** toolchain from `rust-toolchain.toml` (the build vendors and patches `std` for WASI). Build just the Rust commands: - -```bash -make build-wasm-rust # runs: cd native && make wasm -``` - -### C - -C-based commands (e.g. `sqlite3`, `unzip`, `wget`, `zip`) live in `registry/native/c/` and compile with a **wasi-sdk** clang toolchain. Build just the C commands: - -```bash -make build-wasm-c # runs: cd native/c && make programs && make install -``` +Each package's build then runs the **agentos-toolchain** lifecycle: `agentos-toolchain stage` copies the binaries listed in the package's `agentos-package.json` into its `bin/`, and `agentos-toolchain build` assembles the clean `dist/package/` dir with a `bin` map in its `package.json` and packs it into `dist/package.aospkg` (the `{ packagePath }` target). ## Add a new command package -1. Add the command source under `registry/native/crates/` (Rust) or `registry/native/c/` (C). -2. Create `registry/software//` as an `@agentos-software/` npm package that exports a `{ packageDir }` descriptor pointing at its package directory. -3. Add a copy rule to the `copy-wasm` target mapping the built binary into `registry/software//wasm/`. +1. Add the command source as `registry/native/crates/commands//` (cargo package `cmd-`; Rust) or under `registry/native/c/` (C). +2. Create `registry/software//` as an `@agentos-software/` npm package that exports a `{ packagePath }` descriptor pointing at `dist/package.aospkg`. +3. Declare the shipped binaries in its `agentos-package.json`: `{ "commands": [""] }` (plus `aliases`/`stubs` if needed). 4. If it belongs in a meta-package (e.g. `common` or `build-essential`), add it there. -5. Verify with `make copy-wasm && make build && make test`. +5. Verify with `just registry-native-cmd && just registry-build ` and `just registry-test`. ## Let an agent build it @@ -56,15 +44,17 @@ This is a mechanical, well-scoped task, so you can hand it to a coding agent. A ```text Add a WASM command package for `` to the secure-exec registry: -- put the Rust source under registry/native/crates/ (or C under registry/native/c/), +- put the Rust source at registry/native/crates/commands// as a cargo + package named cmd-, - create registry/software// as an @agentos-software/ npm - package that exports a { packageDir } descriptor, -- add a copy-wasm rule mapping the built binary into its wasm/ directory, -then run `make copy-wasm && make build && make test` and fix any failures. + package that exports a { packagePath } descriptor and declares the command in + its agentos-package.json, +then run `just registry-native-cmd && just registry-build ` +and `just registry-test`, and fix any failures. ``` ## Using the registry -Install a published package and pass it to `software`. Registry WASM packages are `{ packageDir }` descriptors — import and pass them directly: +Install a published package and pass it to `software`. Registry WASM packages are `{ packagePath }` descriptors — import and pass them directly: -Meta-packages bundle a full set, e.g. `@agentos-software/common` (coreutils, sed, grep, gawk, findutils, diffutils, tar, gzip). Run the commands from the client; see [Processes & Shell](/docs/processes). Browse the full catalog on the [Registry](/registry), and see the package descriptor in [Software Definition](/docs/custom-software/definition). \ No newline at end of file +Meta-packages bundle a full set, e.g. `@agentos-software/common` (coreutils, sed, grep, gawk, findutils, diffutils, tar, gzip). Run the commands from the client; see [Processes & Shell](/docs/processes). Browse the full catalog on the [Registry](/registry), and see the package descriptor in [Software Definition](/docs/custom-software/definition). To ship your package to npm or use a local build, see [Publishing Packages](/docs/custom-software/publishing). \ No newline at end of file diff --git a/website/public/docs/docs/custom-software/definition.md b/website/public/docs/docs/custom-software/definition.md index c856c65687..acdeed3557 100644 --- a/website/public/docs/docs/custom-software/definition.md +++ b/website/public/docs/docs/custom-software/definition.md @@ -1,10 +1,10 @@ # Software Definition -The software-package definition for custom commands and agents in an agentOS VM: a package is a directory, declared with defineSoftware({ packageDir }). +The software-package definition for custom commands and agents in an agentOS VM: a package is a packed .aospkg (or a package directory), declared with defineSoftware({ packagePath }). **Software** is anything you install into a VM — **commands** (executables in a package's `bin/`) or an **agent** (a package that also exposes an ACP session). -A package is a **self-contained directory**: package it first, then point `defineSoftware()` at it with `{ packageDir }`. The package's name, optional agent block, and any files/env it provides all live in an `agentos-package.json` at the root of that directory — the sidecar reads it when it mounts the package, so the client only forwards the directory. Pick the quickstart that matches what you're packaging. +A package is **self-contained**: package it first, then point `defineSoftware()` at it with `{ packagePath }` — the packed `.aospkg` the toolchain emits, or (for local development) the package directory itself. The package's name, optional agent block, and any files/env it provides are authored in an `agentos-package.json` next to your sources; the toolchain compiles that JSON into the `.aospkg`'s embedded manifest at pack time (the JSON itself is never shipped into the VM). Pick the quickstart that matches what you're packaging. ## Quickstart @@ -73,37 +73,39 @@ An agent is a Node.js or WASM package (packaged exactly as above) whose `agentos ### The descriptor -A software entry is just a pointer to the packaged directory: +A software entry is just a pointer to the packed package: ```ts defineSoftware({ - packageDir: string, // absolute host path to the self-contained package directory + packagePath: string, // absolute host path to the packed .aospkg + // (or a package directory, for local development) }) ``` -`packageDir` must contain **only the package** — a `package.json` with a `bin` map, the runtime -files (`bin/`, a flat `node_modules`), and an `agentos-package.json`. It is mounted read-only, so -**don't point it at a source root**: that drags `src/`, dev `node_modules/`, `tsconfig`, and build -caches into the VM. Point it at a clean build output — `pack` and the WASM assemble step both emit -one. +The normal `packagePath` is the `dist/package.aospkg` that `agentos-toolchain build`/`pack` emit — +a single file holding the package manifest, a precomputed mount index, and the package's mount tar. +A directory is accepted for local development; it must contain **only the package** — a +`package.json` with a `bin` map, the runtime files (`bin/`, a flat `node_modules`), and an +`agentos-package.json`. It is mounted read-only, so **don't point it at a source root**: that drags +`src/`, dev `node_modules/`, `tsconfig`, and build caches into the VM. -`pack` already emits a clean directory. For a package you build by hand (e.g. compiled WASM), -assemble a `dist/package/` holding just `package.json` + `bin/` and point `packageDir` there — -never at the workspace root: +`pack` already emits the packed `.aospkg`. For a package you build by hand (e.g. compiled WASM), +run `agentos-toolchain build` to assemble `dist/package/` and pack `dist/package.aospkg`, then +point `packagePath` there — never at the workspace root: ```ts -// dist/package/ ← { package.json (name, version, bin), bin/…, agentos-package.json } -const packageDir = resolve(import.meta.dirname, "dist/package"); -export default defineSoftware({ packageDir }); +const packagePath = resolve(import.meta.dirname, "dist/package.aospkg"); +export default defineSoftware({ packagePath }); ``` ### `agentos-package.json` -The package's name, optional agent block, and any files/env it provides live in an -`agentos-package.json` at the root of `packageDir`. The sidecar reads it when it mounts the -package, so this metadata never travels on the wire. For command/WASM packages it is **generated** -for you (name from `package.json`); for agents you author the `agent` block (or -`agentos-toolchain pack --agent ` writes it). +The package's name, optional agent block, and any files/env it provides are authored in an +`agentos-package.json` at the package root. It is **toolchain input**: at pack time it is compiled +into the `.aospkg`'s embedded manifest (which is what the sidecar reads) and stripped from the +packed files, so the JSON never ships into the VM and the metadata never travels on the wire. For +command/WASM packages it is **generated** for you (name from `package.json`); for agents you +author the `agent` block (or `agentos-toolchain pack --agent ` writes it). ```jsonc { diff --git a/website/public/docs/docs/custom-software/publishing.md b/website/public/docs/docs/custom-software/publishing.md new file mode 100644 index 0000000000..2abe9da68e --- /dev/null +++ b/website/public/docs/docs/custom-software/publishing.md @@ -0,0 +1,80 @@ +# Publishing Packages + +Build, publish, and consume agentOS packages — locally, from npm, or from your own repo. + +agentOS packages — WASM command sets and packed JS agents alike — go through one lifecycle, owned by the **`@rivet-dev/agentos-toolchain`** CLI. This page covers the full flow: building a package, publishing it to npm, and wiring a consumer at either a published version or a local checkout. + +## The lifecycle + +Every package is an npm package whose default export points at a self-contained runtime dir (`dist/package/`) that the sidecar projects under `/opt/agentos//`. The toolchain provides four subcommands: + +| Command | What it does | +|---|---| +| `stage --commands-dir ` | Populate `bin/` from a directory of compiled binaries, per the `commands` / `aliases` / `stubs` lists in the package's `agentos-package.json`. | +| `build` | Assemble `dist/package/` from `bin/` (+ optional `share/`) and pack it into `dist/package.aospkg` — the runtime artifact with the embedded manifest (the `agentos-package.json` is pack-time input, not shipped). | +| `pack` | Build a self-contained node-closure package from an npm package or local dir (JS agents; validates headers, rejects native addons). | +| `publish` | Publish the built package to npm. Dist-tag is **`dev` by default**; the `latest` pointer only moves with an explicit `--latest`. | + +## Building + +In the AgentOS registry, the `just` recipes drive the toolchain (see [Building Binaries](/docs/custom-software/building-wasm)): + +```bash +just registry-native # compile the native wasm binaries (once per checkout) +just registry-build # stage + assemble every registry package +just registry-build coreutils # ... or one package +just registry-status # inspect: version, staged bin/, assembled dist +``` + +## Publishing + +Registry packages **version independently** — each package carries its own semver in its `package.json`. Bump and commit the version, then: + +```bash +just registry-publish coreutils # publish under dist-tag `dev` +just registry-publish coreutils my-branch # ... under a custom tag +just registry-publish coreutils latest # DELIBERATE release: moves `latest` +just registry-publish-all # every built software package, tag `dev` +``` + +Consumers installing `@agentos-software/` with no tag resolve `latest`, so `latest` is reserved for deliberate releases — a dev publish can never clobber what users install. + +## Consuming published packages + +In agent-os, the `@agentos-software/*` packages are pinned **per-package** in the workspace catalog. Manage the pins with the `just` recipes (never hand-edit them): + +```bash +just agentos-pkgs-status # current mode + pinned versions +just agentos-pkgs-set-version coreutils 0.3.1 # pin one package +just agentos-pkgs-update # re-pin all from the `latest` dist-tag +just agentos-pkgs-update dev # ... or from another tag +``` + +## Local development + +AgentOS consumes local registry builds by default because the registry packages +are pnpm workspace members. Build the native commands with `just registry-native` +and assemble packages with `just registry-build`; no sibling checkout or +published package is required while iterating. + +Published-version pins exist only in release validation and downstream +consumers. The AgentOS workspace itself stays self-contained. + +## Publishing from your own repo + +The toolchain is not registry-specific — any repo can produce and publish agentOS packages with `npx @rivet-dev/agentos-toolchain`: + +```bash +# a package dir with package.json + agentos-package.json + your compiled binaries +npx @rivet-dev/agentos-toolchain stage --commands-dir ./build/wasm +npx @rivet-dev/agentos-toolchain build +npx @rivet-dev/agentos-toolchain publish --tag dev # or --latest for a release +``` + +For a JS agent, `pack` replaces `stage`/`build`: + +```bash +npx @rivet-dev/agentos-toolchain pack . --out dist/package --agent my-acp-entrypoint +``` + +The published package is a plain npm dependency — consumers import its descriptor and pass it to `software` exactly like the registry packages. See [Software Definition](/docs/custom-software/definition) for the descriptor shape. \ No newline at end of file diff --git a/website/public/docs/docs/filesystem.md b/website/public/docs/docs/filesystem.md index de857aa2d1..3633cd6788 100644 --- a/website/public/docs/docs/filesystem.md +++ b/website/public/docs/docs/filesystem.md @@ -20,7 +20,7 @@ Mount a Google Drive folder with the built-in `google_drive` plugin. Use the built-in `memory` plugin for an ephemeral mounted directory in the native RivetKit `agentOS()` actor. -Use `mountFs()` for a callback-backed JS filesystem driver. The driver must live in the same JS process as the `AgentOs` instance, such as direct core usage or a custom RivetKit actor that owns an `AgentOs` instance. +Use `mountFs()` for a callback-backed JS filesystem driver. The driver must live in the same JS process as the `AgentOs` instance, such as direct core usage or a custom RivetKit actor that owns an `AgentOs` instance. `mountFs()` works on a running VM too — it returns a promise that resolves once the mount is visible to guest code, and rejects if delivery to the runtime fails. The native `agentOS()` actor cannot accept `{ driver }` mounts in config because JS callback objects are not serializable across the native plugin boundary. Use `plugin` mounts there. diff --git a/website/public/docs/docs/quickstart.md b/website/public/docs/docs/quickstart.md index 72a955ae84..6079271c04 100644 --- a/website/public/docs/docs/quickstart.md +++ b/website/public/docs/docs/quickstart.md @@ -18,7 +18,7 @@ Set up an agentOS actor, create a session, and run your first coding agent. 1. **Install** - **@rivet-dev/agentos** — Actor framework with built-in persistence and orchestration - - **@agentos-software/pi** — [Pi](https://github.com/mariozechner/pi-coding-agent) coding agent (Claude Code, Codex, and OpenCode coming soon) + - **@agentos-software/pi** — [Pi](https://github.com/mariozechner/pi-coding-agent) coding agent. [Claude Code](/docs/agents/claude), [Codex](/docs/agents/codex), and [OpenCode](/docs/agents/opencode) install the same way. ```bash npm install @rivet-dev/agentos @agentos-software/pi diff --git a/website/public/docs/docs/resource-limits.md b/website/public/docs/docs/resource-limits.md index 2320d649c2..6a45158c61 100644 --- a/website/public/docs/docs/resource-limits.md +++ b/website/public/docs/docs/resource-limits.md @@ -1,17 +1,17 @@ # Resource Limits -Cap per-VM processes, file descriptors, sockets, and filesystem bytes so guest code can never exhaust the host. +Cap per-VM resources, JavaScript CPU/wall-clock time, Python execution, and WASM runtime work so guest code can never exhaust the host. -Every agentOS VM runs with **per-VM resource caps**. Runaway or malicious guest code can exhaust its own VM, but it can never starve the host or any sibling VM. +Every agentOS VM runs with **per-VM resource and runtime caps**. Runaway or malicious guest code can exhaust its own VM, but it can never starve the host or any sibling VM. - **Bounded by default**: each VM ships with conservative caps. Unset fields fall back to built-in defaults that match the runtime's historical constants. - **Per-VM**: every VM gets its own budget. Limits are not shared across VMs. -- **Enforced by the kernel**: a guest that exceeds a cap fails inside the VM (out-of-memory, `EMFILE`, `EAGAIN`, etc.). The host is never affected. +- **Enforced by the sidecar/runtime**: a guest that exceeds a cap fails inside the VM (out-of-memory, `EMFILE`, `EAGAIN`, runtime timeout, etc.). The host is never affected. - **Operator-raisable**: the operator (the trusted process that creates the VM) may raise any cap for trusted workloads. Guest code can never raise its own caps. ## Setting limits -Set caps on the `limits` object in the `agentOS` config. Limits are grouped by subsystem (`resources` and more). Omitted limits keep their secure default. +Set caps on the `limits` object in the `agentOS` config. Limits are grouped by subsystem (`resources`, `jsRuntime`, `python`, `wasm`, and more). Omitted limits keep their secure default. ## Available caps @@ -21,14 +21,41 @@ Set caps on the `limits` object in the `agentOS` config. Limits are grouped by s | `resources.maxOpenFds` | Open file descriptors | Exhausting the table fails with `EMFILE` / `ENFILE`. | | `resources.maxSockets` | Open sockets in the socket table | Bounds concurrent connections; excess `connect`/`accept` fail. | | `resources.maxFilesystemBytes` | Total bytes stored in the virtual filesystem | Bounds VFS storage; writes past the budget fail with a no-space error. | +| `resources.maxWasmFuel` | WASM execution budget | Bounds WASM execution work; unset means no explicit fuel budget. | +| `resources.maxWasmMemoryBytes` | WASM linear memory, in bytes | Default is `128 MiB`. | | `resources.maxWasmStackBytes` | Maximum WASM call-stack size, in bytes | Deep recursion fails with a stack overflow instead of crashing the VM. | +| `jsRuntime.v8HeapLimitMb` | Guest JavaScript V8 heap, in MiB | Default is `128`. | +| `jsRuntime.cpuTimeLimitMs` | Active JavaScript CPU time | Default is `30000`; `0` disables the CPU watchdog. | +| `jsRuntime.wallClockLimitMs` | JavaScript elapsed wall-clock backstop | Default is `0`, disabled. Use this for finite commands, not long-lived adapters. | +| `jsRuntime.importCacheMaterializeTimeoutMs` | Node import-cache materialization timeout | Default is `30000`. | +| `jsRuntime.syncRpcWaitTimeoutMs` | JavaScript sync host-RPC wait | Unset keeps the engine default, currently `30000`. | +| `python.executionTimeoutMs` | Python execution wall-clock timeout | Default is `300000`. | +| `python.maxOldSpaceMb` | Pyodide runner V8 old-space heap, in MiB | Default is `0`, which keeps the engine default. | +| `wasm.prewarmTimeoutMs` | WASM compile-cache warmup timeout | Default is `30000`. | +| `wasm.runnerHeapLimitMb` | Trusted WASI/WASM runner V8 heap, in MiB | Default is `2048`; this is not guest linear memory. | ## Behavior at the limit - **WASM stack**: deep recursion throws a stack-overflow error in the guest, never a host crash. +- **JavaScript CPU time**: CPU-bound loops terminate with a CPU-budget error once active JS CPU exceeds `jsRuntime.cpuTimeLimitMs`. +- **JavaScript wall time**: awaiting or blocked JS terminates only when you set `jsRuntime.wallClockLimitMs`; the default is disabled for long-lived adapters. - **Filesystem bytes**: writing past the VFS budget fails with a no-space error to the guest. - **Counts (fds / processes / sockets)**: hitting a table cap returns the standard POSIX errno (`EMFILE`, `EAGAIN`, etc.), exactly as a real Linux kernel would under `ulimit`. +## Sidecar liveness + +Separate from the guest caps above, the host detects a dead or wedged sidecar +process by silence, not by per-request deadlines. The sidecar emits a liveness +heartbeat every 10 seconds from a dedicated thread — so it keeps beating even +mid-way through a long turn — and the host treats 30 seconds with no inbound +frames at all as a dead sidecar: it kills the process and fails in-flight +requests with a typed `SidecarSilenceTimeout` error. + +Because liveness is silence-based, individual requests have no time limit of +their own: an agent turn may legitimately run for many minutes without being +torn down. Neither the heartbeat cadence nor the silence window is +configurable — they are fixed protocol constants. + ## Warnings & observability Limits are observable, not just enforced. Every bound — resource caps and the diff --git a/website/public/docs/docs/versus-sandbox.md b/website/public/docs/docs/versus-sandbox.md index 0dbb9a7f24..062161ae53 100644 --- a/website/public/docs/docs/versus-sandbox.md +++ b/website/public/docs/docs/versus-sandbox.md @@ -13,7 +13,7 @@ When to use the lightweight agentOS VM, a full sandbox, or both together. | **Cost** | Very low. Runs in your process. | Pay per second of uptime. | | **Startup** | Near-zero cold start (~6 ms). | Seconds to spin up. | | **Backend integration** | Direct. [Bindings](/docs/bindings) call your functions with zero latency. | Indirect. Requires network calls back to your backend. | -| **API keys** | Stay on the server via the [LLM gateway](/docs/llm-gateway). | Must be injected into the sandbox environment. | +| **Credentials** | Stay on the host. [Bindings](/docs/bindings) run your functions server-side; agents see only inputs and outputs. | Must be injected into the sandbox environment. | | **Permissions** | Granular, deny-by-default. | Coarse-grained (container-level). | | **Infrastructure** | `npm install` | Vendor account + API keys. | | **Best for** | Coding, file manipulation, scripting, API calls, orchestration. | Browsers, desktop automation, native compilation, dev servers. | diff --git a/website/public/images/agent-os/agentos-hero-logo-animated.svg b/website/public/images/agent-os/agentos-hero-logo-animated.svg index 9c0d1915c6..2433b74814 100644 --- a/website/public/images/agent-os/agentos-hero-logo-animated.svg +++ b/website/public/images/agent-os/agentos-hero-logo-animated.svg @@ -63,7 +63,7 @@ - + diff --git a/website/public/images/frameworks/eve.svg b/website/public/images/frameworks/eve.svg new file mode 100644 index 0000000000..c701416c96 --- /dev/null +++ b/website/public/images/frameworks/eve.svg @@ -0,0 +1 @@ + diff --git a/website/public/images/frameworks/flue.svg b/website/public/images/frameworks/flue.svg new file mode 100644 index 0000000000..ea7569fc00 --- /dev/null +++ b/website/public/images/frameworks/flue.svg @@ -0,0 +1 @@ + diff --git a/website/public/images/registry/linux.svg b/website/public/images/registry/linux.svg new file mode 100644 index 0000000000..05ca79675e --- /dev/null +++ b/website/public/images/registry/linux.svg @@ -0,0 +1 @@ +Linux \ No newline at end of file diff --git a/website/public/images/registry/nodejs.svg b/website/public/images/registry/nodejs.svg new file mode 100644 index 0000000000..3cc5893e0c --- /dev/null +++ b/website/public/images/registry/nodejs.svg @@ -0,0 +1 @@ +Node.js \ No newline at end of file diff --git a/website/public/images/registry/python.svg b/website/public/images/registry/python.svg new file mode 100644 index 0000000000..e1b14ec64b --- /dev/null +++ b/website/public/images/registry/python.svg @@ -0,0 +1 @@ +Python \ No newline at end of file diff --git a/website/src/components/marketing/diagrams/AgentSessionDemo.tsx b/website/src/components/marketing/diagrams/AgentSessionDemo.tsx new file mode 100644 index 0000000000..70c96bda03 --- /dev/null +++ b/website/src/components/marketing/diagrams/AgentSessionDemo.tsx @@ -0,0 +1,601 @@ +'use client'; + +import { useEffect, useId, useMemo, useLayoutEffect, useRef, useState } from 'react'; +import { FileCode2, FileText, Play, RotateCcw, SquarePen } from 'lucide-react'; +import { AnimatePresence, motion, useReducedMotion } from 'framer-motion'; +import { InkPanel } from '../editorial/InkPanel'; + +// --------------------------------------------------------------------------- +// Recorded agent coding sessions, played back line by line inside an ink +// terminal. A segmented control picks the runtime and replays the same task +// with the agent writing that language; the active runtime's description and +// docs link sit under the control and swap with it. A filesystem rail on the +// right shows /home/agentos changing as the session runs, so the script +// visibly works against a whole OS rather than a chat box. The Python session +// runs the interpreter natively (no shell lines); Node and bash keep their +// commands. +// --------------------------------------------------------------------------- + +type SessionLineKind = 'user' | 'agent' | 'cmd' | 'run' | 'out' | 'script'; + +interface SessionLine { + kind: SessionLineKind; + text: string; +} + +type ScriptLang = 'js' | 'python' | 'bash'; + +interface SessionFile { + name: string; + size: string; + kind: 'code' | 'data'; + // Session line index that creates this file; it appears in the rail once + // that line has finished playing. + afterIndex: number; +} + +interface SessionTab { + key: string; + title: string; + description: string; + docsHref: string; + docsLabel: string; + iconSrc: string; + script: { fileName: string; lang: ScriptLang; code: string }; + session: SessionLine[]; + files: SessionFile[]; +} + +// One task, three languages: fetch last week's issues, roll them up, write +// report.md. The label counts sum to the reported total (9+6+5+3 = 23), and +// the bash tab lists them alphabetically because its jq pipeline sorts. +const REPORT_JS = `import { writeFileSync } from "node:fs"; + +const since = new Date(Date.now() - 7 * 864e5).toISOString(); +const url = \`https://api.github.com/repos/acme/shop/issues?since=\${since}\`; +const issues = await (await fetch(url)).json(); + +const counts = {}; +for (const { labels } of issues) + for (const { name } of labels) counts[name] = (counts[name] ?? 0) + 1; + +const rows = Object.entries(counts).sort((a, b) => b[1] - a[1]); +writeFileSync("report.md", [ + \`# Issues, last 7 days: \${issues.length}\`, + ...rows.map(([label, n]) => \`- \${label}: \${n}\`), +].join("\\n"));`; + +const REPORT_PY = `import json, datetime, urllib.request +from collections import Counter + +since = (datetime.date.today() - datetime.timedelta(days=7)).isoformat() +url = f"https://api.github.com/repos/acme/shop/issues?since={since}" +issues = json.load(urllib.request.urlopen(url)) + +counts = Counter(l["name"] for i in issues for l in i["labels"]) + +lines = [f"# Issues, last 7 days: {len(issues)}"] +lines += [f"- {label}: {n}" for label, n in counts.most_common()] +report = "\\n".join(lines) +open("report.md", "w").write(report) +print(report)`; + +const REPORT_SH = `#!/bin/bash +set -euo pipefail + +since=$(date -u -d '7 days ago' +%Y-%m-%d) +url="https://api.github.com/repos/acme/shop/issues?since=$since" +curl -s "$url" > issues.json + +{ + echo "# Issues, last 7 days: $(jq length issues.json)" + jq -r '[.[].labels[].name] | sort | group_by(.) + | .[] | "- \\(.[0]): \\(length)"' issues.json +} > report.md`; + +const REPORT_OUT = ['- bug: 9', '- api: 6', '- ui: 5', '- docs: 3']; + +const shellSession = (runCmd: string, reportLines: string[]): SessionLine[] => [ + { kind: 'user', text: "generate a report of last week's issues" }, + { kind: 'agent', text: 'Writing a script to fetch them and build the report.' }, + { kind: 'script', text: '' }, + { kind: 'cmd', text: runCmd }, + { kind: 'cmd', text: 'cat report.md' }, + { kind: 'out', text: '# Issues, last 7 days: 23' }, + ...reportLines.map((text): SessionLine => ({ kind: 'out', text })), + { kind: 'agent', text: '23 issues last week; bug reports lead with 9.' }, +]; + +// Python runs on the native interpreter: the run step replaces shell lines +// and the script prints the report it just wrote. +const pythonSession: SessionLine[] = [ + { kind: 'user', text: "generate a report of last week's issues" }, + { kind: 'agent', text: 'Writing a script to fetch them and build the report.' }, + { kind: 'script', text: '' }, + { kind: 'run', text: 'Run report.py' }, + { kind: 'out', text: '# Issues, last 7 days: 23' }, + ...REPORT_OUT.map((text): SessionLine => ({ kind: 'out', text })), + { kind: 'agent', text: '23 issues last week; bug reports lead with 9.' }, +]; + +const TABS: SessionTab[] = [ + { + key: 'nodejs', + title: 'JavaScript & TypeScript', + description: 'Node v22 compatible on native V8 isolates. node, npm, and npx on the PATH at full JIT speed.', + docsHref: '/docs/nodejs-runtime', + docsLabel: 'Node.js runtime docs', + iconSrc: '/images/registry/nodejs.svg', + script: { fileName: 'report.mjs', lang: 'js', code: REPORT_JS }, + session: shellSession('node report.mjs', REPORT_OUT), + files: [ + { name: 'report.mjs', size: '486 B', kind: 'code', afterIndex: 2 }, + { name: 'report.md', size: '96 B', kind: 'data', afterIndex: 3 }, + ], + }, + { + key: 'python', + title: 'Python', + description: 'CPython 3.13 with pip. Native wheels like numpy and pandas work.', + docsHref: '/docs/python-runtime', + docsLabel: 'Python runtime docs', + iconSrc: '/images/registry/python.svg', + script: { fileName: 'report.py', lang: 'python', code: REPORT_PY }, + session: pythonSession, + files: [ + { name: 'report.py', size: '451 B', kind: 'code', afterIndex: 2 }, + { name: 'report.md', size: '96 B', kind: 'data', afterIndex: 3 }, + ], + }, + { + key: 'bash', + title: 'Linux shell', + description: 'A POSIX userland with a process table, PTYs, TCP and UDP with DNS, and deny-by-default permissions.', + docsHref: '/docs/architecture', + docsLabel: 'Kernel & shell docs', + iconSrc: '/images/registry/linux.svg', + script: { fileName: 'report.sh', lang: 'bash', code: REPORT_SH }, + session: shellSession('bash report.sh', ['- api: 6', '- bug: 9', '- docs: 3', '- ui: 5']), + files: [ + { name: 'report.sh', size: '338 B', kind: 'code', afterIndex: 2 }, + { name: 'issues.json', size: '18 KB', kind: 'data', afterIndex: 3 }, + { name: 'report.md', size: '96 B', kind: 'data', afterIndex: 3 }, + ], + }, +]; + +const WINDOW_TITLE = 'agentos vm · /home/agentos'; +const CAPTION = 'The agent writes one program instead of a chain of tool calls.'; + +// --- Tiny dark-palette tokenizer for the script block --------------------- +// The site's highlightCodeHtml is tuned for light panels and JS only; the +// script hero sits on ink and covers three languages, so it gets its own +// minimal pass: comments, strings, keywords, and shell variables. + +type TokenType = 'kw' | 'str' | 'com' | 'var' | 'text'; + +interface Token { + type: TokenType; + value: string; +} + +const TOKEN_CLASS: Record, string> = { + kw: 'text-sage', + str: 'text-[#CFA379]', + com: 'italic text-cream/40', + var: 'text-[#CFA379]', +}; + +const SCRIPT_RULES: Record = { + js: /(\/\/.*)|(`(?:\\.|[^`])*`|"(?:\\.|[^"])*"|'(?:\\.|[^'])*')|()\b(import|from|const|let|var|await|async|function|return|new|for|of|if|else|export)\b/g, + python: + /(#.*)|((?:[frbFRB]{1,2})?(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'))|()\b(import|from|def|return|for|in|if|else|with|as|class|lambda)\b/g, + bash: /(#.*)|("(?:\\.|[^"])*"|'[^']*')|(\$\{?\w+\}?)|\b(set|echo|if|then|else|fi|for|do|done)\b/g, +}; + +// Tokenizes the whole script (strings may span lines), then splits into +// per-line token runs for rendering. +const tokenizeScript = (code: string, lang: ScriptLang): Token[][] => { + const rule = new RegExp(SCRIPT_RULES[lang].source, 'g'); + const tokens: Token[] = []; + let last = 0; + for (let match = rule.exec(code); match; match = rule.exec(code)) { + if (match.index > last) tokens.push({ type: 'text', value: code.slice(last, match.index) }); + const [, com, str, shVar, kw] = match; + const type: TokenType = com !== undefined ? 'com' : str !== undefined ? 'str' : shVar ? 'var' : kw ? 'kw' : 'text'; + tokens.push({ type, value: match[0] }); + last = match.index + match[0].length; + } + if (last < code.length) tokens.push({ type: 'text', value: code.slice(last) }); + + const lines: Token[][] = [[]]; + for (const token of tokens) { + token.value.split('\n').forEach((part, idx) => { + if (idx > 0) lines.push([]); + if (part) lines[lines.length - 1].push({ type: token.type, value: part }); + }); + } + return lines; +}; + +// --- Playback -------------------------------------------------------------- +// Pacing in clock ms. `user` and `cmd` lines type per character; the rest +// appear whole after their lead-in. The script keeps the longest pause so the +// eye can land on it; everything else is brisk. +const START_DELAY = 250; +const RUN_DELAY = 350; +const LEAD: Record = { user: 200, agent: 550, cmd: 480, run: 600, out: 60, script: 700 }; +const CHAR_MS: Partial> = { user: 17, cmd: 8 }; + +interface ScheduledLine extends SessionLine { + start: number; + dur: number; +} + +const buildSchedule = (lines: SessionLine[]): { schedule: ScheduledLine[]; total: number } => { + let t = START_DELAY; + const schedule = lines.map((line, i) => { + const prev = lines[i - 1]?.kind; + const lead = line.kind === 'out' && (prev === 'cmd' || prev === 'run') ? RUN_DELAY : LEAD[line.kind]; + const start = t + lead; + const dur = (CHAR_MS[line.kind] ?? 0) * line.text.length; + t = start + dur; + return { ...line, start, dur }; + }); + return { schedule, total: t }; +}; + +// Drives the playback clock with an accumulated per-frame delta (capped so a +// backgrounded tab resumes where it paused instead of jumping to the end). +// The reset runs in a layout effect so a tab switch never flashes the new +// transcript fully-played before the clock restarts. +const usePlaybackClock = (total: number, running: boolean, playKey: number, skipToEnd: boolean) => { + const [clock, setClock] = useState(skipToEnd ? total : 0); + + useLayoutEffect(() => { + if (skipToEnd) { + setClock(total); + return; + } + if (!running) return; + setClock(0); + let raf = 0; + let last = performance.now(); + let elapsed = 0; + const step = (now: number) => { + elapsed += Math.min(now - last, 100); + last = now; + setClock(elapsed); + if (elapsed < total) raf = requestAnimationFrame(step); + }; + raf = requestAnimationFrame(step); + return () => cancelAnimationFrame(raf); + }, [total, running, playKey, skipToEnd]); + + return clock; +}; + +const Cursor = ({ blink }: { blink?: boolean }) => ( +