diff --git a/crates/client/src/agent_os.rs b/crates/client/src/agent_os.rs index 5391458f2a..63638e55ee 100644 --- a/crates/client/src/agent_os.rs +++ b/crates/client/src/agent_os.rs @@ -26,9 +26,9 @@ use agentos_vm_config as vm_config; use crate::config::{ resolve_host_functions, AgentOsConfig, AgentOsLimits, MountConfig, ResolvedHostFunction, - ResolvedHostFunctions, RootFilesystemConfig, - RootFilesystemKind, RootFilesystemMode as ConfigRootFilesystemMode, RootLowerInput, - SidecarJsBridgeCall, SidecarJsBridgeCallback, TimerScheduleDriver, + ResolvedHostFunctions, RootFilesystemConfig, RootFilesystemKind, + RootFilesystemMode as ConfigRootFilesystemMode, RootLowerInput, SidecarJsBridgeCall, + SidecarJsBridgeCallback, TimerScheduleDriver, }; use crate::cron::CronManager; use crate::error::ClientError; diff --git a/crates/client/src/config.rs b/crates/client/src/config.rs index 3600fc9dd9..1980e697c9 100644 --- a/crates/client/src/config.rs +++ b/crates/client/src/config.rs @@ -307,9 +307,9 @@ fn is_command_name(name: &str) -> bool { && !name.starts_with('-') && !name.ends_with('-') && !name.contains("--") - && name - .chars() - .all(|character| character.is_ascii_lowercase() || character.is_ascii_digit() || character == '-') + && name.chars().all(|character| { + character.is_ascii_lowercase() || character.is_ascii_digit() || character == '-' + }) } fn to_command_name(kind: &str, key: &str) -> Result { diff --git a/crates/client/src/lib.rs b/crates/client/src/lib.rs index 1c48c75b49..85daf0adf4 100644 --- a/crates/client/src/lib.rs +++ b/crates/client/src/lib.rs @@ -67,13 +67,13 @@ pub use stream::{ByteStream, Subscription}; pub use config::{ node_modules_mount, AcpLimits, AgentOsConfig, AgentOsConfigBuilder, AgentOsLimits, AgentOsSidecarConfig, FsPermissionRule, FsPermissions, HostFunction, HostFunctionCallback, - HostFunctionCollection, HostFunctionCollections, HostFunctionLimits, HttpLimits, JsRuntimeLimits, MountConfig, MountPlugin, - OverlayMountConfig, PackageRef, PatternPermissionRule, PatternPermissions, PermissionMode, - Permissions, PluginLimits, PythonLimits, ResourceLimits, RootFilesystemConfig, - RootFilesystemKind, RootFilesystemMode, RootLowerInput, RulePermissions, ScheduleCallback, - ScheduleDriver, ScheduleEntry, ScheduleHandle, SidecarJsBridgeCall, SidecarJsBridgeCallback, - SoftwareInput, SoftwareKind, TimerScheduleDriver, VmGroupConfig, VmUserAccountConfig, - VmUserConfig, WasmLimits, + HostFunctionCollection, HostFunctionCollections, HostFunctionLimits, HttpLimits, + JsRuntimeLimits, MountConfig, MountPlugin, OverlayMountConfig, PackageRef, + PatternPermissionRule, PatternPermissions, PermissionMode, Permissions, PluginLimits, + PythonLimits, ResourceLimits, RootFilesystemConfig, RootFilesystemKind, RootFilesystemMode, + RootLowerInput, RulePermissions, ScheduleCallback, ScheduleDriver, ScheduleEntry, + ScheduleHandle, SidecarJsBridgeCall, SidecarJsBridgeCallback, SoftwareInput, SoftwareKind, + TimerScheduleDriver, VmGroupConfig, VmUserAccountConfig, VmUserConfig, WasmLimits, }; pub use process::{ diff --git a/crates/client/tests/os_instructions_e2e.rs b/crates/client/tests/os_instructions_e2e.rs index 8b78ac7145..9c39a8fb98 100644 --- a/crates/client/tests/os_instructions_e2e.rs +++ b/crates/client/tests/os_instructions_e2e.rs @@ -13,8 +13,9 @@ use std::path::Path; use std::sync::Arc; use agentos_client::config::{ - node_modules_mount, AgentOsConfig, AgentOsSidecarConfig, FsPermissions, HostFunction, HostFunctionCollection, - HostFunctionCollections, PackageRef, PatternPermissions, PermissionMode, Permissions, + node_modules_mount, AgentOsConfig, AgentOsSidecarConfig, FsPermissions, HostFunction, + HostFunctionCollection, HostFunctionCollections, PackageRef, PatternPermissions, + PermissionMode, Permissions, }; use agentos_client::{AgentOs, OpenSessionInput}; use agentos_vm_config::VmSqliteDescriptor; diff --git a/crates/native-sidecar-core/src/permissions.rs b/crates/native-sidecar-core/src/permissions.rs index 0689797910..ceddfddeac 100644 --- a/crates/native-sidecar-core/src/permissions.rs +++ b/crates/native-sidecar-core/src/permissions.rs @@ -114,10 +114,7 @@ pub fn resolve_permissions_policy( child_process: requested.child_process.clone().or(defaults.child_process), process: requested.process.clone().or(defaults.process), env: requested.env.clone().or(defaults.env), - host_function: requested - .host_function - .clone() - .or(defaults.host_function), + host_function: requested.host_function.clone().or(defaults.host_function), } } diff --git a/crates/native-sidecar/src/execution/mod.rs b/crates/native-sidecar/src/execution/mod.rs index b123305f94..fec608f1d7 100644 --- a/crates/native-sidecar/src/execution/mod.rs +++ b/crates/native-sidecar/src/execution/mod.rs @@ -137,14 +137,14 @@ use crate::state::{ GuestUnixListenerRoute, HostFunctionExecution, HostNetTransferDescription, HostNetTransferDescriptionRegistry, Http2BridgeEvent, Http2ResponseSender, Http2RuntimeSnapshot, Http2SessionCommand, Http2SessionSnapshot, Http2SocketSnapshot, - JavascriptHttp2LoopbackTarget, - JavascriptHttpLoopbackTarget, JavascriptSocketFamily, JavascriptSocketPathContext, - JavascriptTcpListenerEvent, JavascriptTcpSocketEvent, JavascriptTlsBridgeOptions, - JavascriptTlsClientHello, JavascriptTlsDataValue, JavascriptTlsMaterial, JavascriptUdpFamily, - JavascriptUdpSocketEvent, JavascriptUnixListenerEvent, KernelSocketReadinessEvent, - KernelSocketReadinessRegistry, KernelSocketReadinessTarget, ListenerConnectionRetirement, - NativeCapabilityKey, NativePlainSocketCommand, NativeTlsCommand, NativeUdpCommand, - NativeUdpSendPayload, NativeUdpSocketOption, NetworkResourceCounts, PendingChildProcessSync, + JavascriptHttp2LoopbackTarget, JavascriptHttpLoopbackTarget, JavascriptSocketFamily, + JavascriptSocketPathContext, JavascriptTcpListenerEvent, JavascriptTcpSocketEvent, + JavascriptTlsBridgeOptions, JavascriptTlsClientHello, JavascriptTlsDataValue, + JavascriptTlsMaterial, JavascriptUdpFamily, JavascriptUdpSocketEvent, + JavascriptUnixListenerEvent, KernelSocketReadinessEvent, KernelSocketReadinessRegistry, + KernelSocketReadinessTarget, ListenerConnectionRetirement, NativeCapabilityKey, + NativePlainSocketCommand, NativeTlsCommand, NativeUdpCommand, NativeUdpSendPayload, + NativeUdpSocketOption, NetworkResourceCounts, PendingChildProcessSync, PendingChildProcessSyncCompletion, PendingHttpRequest, PendingJavascriptNetConnect, PendingJavascriptNetConnectState, PendingKernelStdin, PendingPythonTcpConnect, PendingTcpSocket, PendingUnixConnectionGuard, PendingUnixSocket, PlainSocketWritePayload, diff --git a/crates/native-sidecar/src/execution/network/unix.rs b/crates/native-sidecar/src/execution/network/unix.rs index 530b353717..704d6336fd 100644 --- a/crates/native-sidecar/src/execution/network/unix.rs +++ b/crates/native-sidecar/src/execution/network/unix.rs @@ -1114,6 +1114,7 @@ pub(in crate::execution) fn defer_vm_local_unix_connect( // ActiveUnixListener moved to crate::state impl ActiveUnixListener { + #[allow(clippy::too_many_arguments)] fn from_virtual( guest_path: String, abstract_path_hex: Option, diff --git a/crates/native-sidecar/src/language_execution.rs b/crates/native-sidecar/src/language_execution.rs index d7e4e97a16..aa72d41093 100644 --- a/crates/native-sidecar/src/language_execution.rs +++ b/crates/native-sidecar/src/language_execution.rs @@ -3088,7 +3088,10 @@ mod inline_host_functions_prefix_tests { fn converts_kebab_case_names_to_identifiers() { assert_eq!(host_function_identifier("orders"), "orders"); assert_eq!(host_function_identifier("order-store"), "orderStore"); - assert_eq!(host_function_identifier("list-open-orders"), "listOpenOrders"); + assert_eq!( + host_function_identifier("list-open-orders"), + "listOpenOrders" + ); } #[test] diff --git a/docs/content/docs/architecture.mdx b/docs/content/docs/architecture.mdx index 265259a706..3cc9b1c7a6 100644 --- a/docs/content/docs/architecture.mdx +++ b/docs/content/docs/architecture.mdx @@ -73,7 +73,7 @@ The client speaks to the agentOS server over the wire. The server runs the **sid ### The VM - **A fully virtualized Linux environment.** Each VM has its own filesystem, process table, and network policy. Two VMs share nothing. -- **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. +- **The unit of isolation.** A crash or runaway in one VM never affects another. - **Where guest code lives.** The agent, the shell, npm packages, and any generated code all run inside the VM, behind the kernel's boundary. ## Anatomy of a Linux VM diff --git a/docs/content/docs/models-and-credentials.mdx b/docs/content/docs/models-and-credentials.mdx index 4429971e33..508a3d2679 100644 --- a/docs/content/docs/models-and-credentials.mdx +++ b/docs/content/docs/models-and-credentials.mdx @@ -6,7 +6,7 @@ skill: true - Choose the model through your **agent adapter**. - Pass provider credentials from trusted server code. -- Credentials are injected at session creation and can be isolated per tenant. +- Credentials are injected at session creation. - The VM does **not** inherit the host `process.env` — keys must be passed explicitly. @@ -18,7 +18,6 @@ Pass LLM provider keys via the `env` option on `openSession`. ## Per-tenant credentials -- Key `getOrCreate` on the tenant id → isolated VM per tenant. - Look up the tenant's key on the server, inject via session `env`. - Keys stay on the server and never reach the client, and one tenant's key never reaches another. diff --git a/docs/content/docs/performance.mdx b/docs/content/docs/performance.mdx index 34fb8cf85d..6d487dae1d 100644 --- a/docs/content/docs/performance.mdx +++ b/docs/content/docs/performance.mdx @@ -85,8 +85,7 @@ Pi session with MCP servers and mounted filesystems. - agentOS runs on hardware you provision, not per-VM-second billing. - Estimate: instance cost per second ÷ concurrent VMs it holds with realistic memory + utilization headroom. -- A VM per task/tenant → strongest isolation; safely reusing a VM for trusted - sequential work amortizes boot + filesystem setup. +- Reusing a VM for sequential work amortizes boot and filesystem setup. - Small short executions benefit most from dense packing; heavy long-lived work depends more on hardware choice and avoiding per-sandbox minimums. diff --git a/examples/llm-credentials/README.md b/examples/llm-credentials/README.md index 09f27235f9..349303c696 100644 --- a/examples/llm-credentials/README.md +++ b/examples/llm-credentials/README.md @@ -9,7 +9,7 @@ A VM never inherits the host `process.env`, so LLM provider keys must be handed ## How it works -The server declares the agent software but holds no credentials. The client passes keys through the `env` option on `openSession`, which injects them into that session's adapter environment. For multi-tenant setups, give each tenant an isolated VM keyed by their id and resolve their key from your own credential store when opening the session. Keys live on the server and are never sent to the browser. +The server declares the agent software but holds no credentials. The client passes keys through the `env` option on `openSession`, which injects them into that session's adapter environment. For multi-tenant setups, resolve each tenant's key from your own credential store when opening the session. Keys live on the server and are never sent to the browser. ## Run it diff --git a/examples/llm-credentials/per-tenant.ts b/examples/llm-credentials/per-tenant.ts index 20355277a1..3ec01b8778 100644 --- a/examples/llm-credentials/per-tenant.ts +++ b/examples/llm-credentials/per-tenant.ts @@ -8,8 +8,8 @@ const client = createClient({ // Stand-in for your own per-tenant credential store. declare function lookupTenantApiKey(tenantId: string): Promise; -// Give each tenant an isolated VM keyed by their tenant id, then inject that -// tenant's API key from your database at session creation. Keys stay on the +// Look up each tenant's API key from your database and inject it at session +// creation. Keys stay on the // server and never reach the client. async function startTenantSession(tenantId: string) { const anthropicApiKey = await lookupTenantApiKey(tenantId); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 41f5e6fdd3..60afb0bbdb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3539,6 +3539,25 @@ importers: specifier: ^5.7.2 version: 5.9.3 + secure-exec/examples/code-mode: + dependencies: + secure-exec: + specifier: workspace:* + version: link:../.. + zod: + specifier: ^4.1.11 + version: 4.3.6 + devDependencies: + '@types/node': + specifier: ^22.10.2 + version: 22.19.15 + tsx: + specifier: ^4.19.2 + version: 4.21.0 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + secure-exec/examples/contexts: dependencies: secure-exec: diff --git a/secure-exec/docs/content/docs/agentos.mdx b/secure-exec/docs/content/docs/agentos.mdx deleted file mode 100644 index 4b80daabe9..0000000000 --- a/secure-exec/docs/content/docs/agentos.mdx +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: "Going Further with agentOS" -description: "Secure Exec is a small API over agentOS. Use agentOS directly for processes, a shell, Python, host functions, and agent sessions." ---- - -Secure Exec is a thin function-style API over -[agentOS](/agentos/docs). Every Secure Exec VM **is** an agentOS VM, so anything -you learn there applies here. - -## Shared reference - -These topics are identical in both, and are documented once in agentOS. - -| Topic | agentOS page | -|---|---| -| Node.js builtins and globals available to guest code | [JavaScript Compatibility](/agentos/docs/javascript-compatibility) | -| Trust boundaries and what the VM guarantees | [Security Model](/agentos/docs/security-model) | -| How the VM, kernel, and sidecar fit together | [Architecture](/agentos/docs/architecture) | -| Cold start, memory, and cost measurements | [Performance](/agentos/docs/performance) | -| Known gaps | [Limitations](/agentos/docs/limitations) | - -## When to use agentOS directly - -Reach for `@rivet-dev/agentos-core` when you need more than running snippets. - -| You need | agentOS | -|---|---| -| Interactive terminals and process trees | [Processes](/agentos/docs/processes) | -| A shell and real Linux tools | [Bash](/agentos/docs/bash) | -| Python execution, with the same `execute` and `evaluate` shape | [Python](/agentos/docs/python) | -| Coding agents such as Claude Code, Codex, and Pi | [Sessions](/agentos/docs/sessions) | - -Both packages can be used in the same process. They share one sidecar. diff --git a/secure-exec/docs/content/docs/api-reference.mdx b/secure-exec/docs/content/docs/api-reference.mdx deleted file mode 100644 index faaa894860..0000000000 --- a/secure-exec/docs/content/docs/api-reference.mdx +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: "API Reference" -description: "Every function, option, and result type exported by the secure-exec package." -skill: true ---- - -## `secure-exec` - -| Export | Signature | Purpose | -|---|---|---| -| `evaluate` | `evaluate(expression, options?)` → `CodeEvaluationResult` | Evaluate one expression in a fresh VM | -| `execute` | `execute(source, options?)` → `CodeExecutionResult` | Run an ES module in a fresh VM | -| `executeFile` | `executeFile(path, options?)` → `CodeExecutionResult` | Run a mounted file in a fresh VM | -| `createVm` | `createVm(vmOptions?)` → `Vm` | Create a VM that lives until you dispose it | -| `init` | `init()` | Start the shared sidecar ahead of time | -| `shutdown` | `shutdown()` | Stop the sidecar and every VM in it | -| `hostDirMount` | `hostDirMount(path, hostPath, { readOnly? })` | Mount a host directory | -| `nodeModulesMount` | `nodeModulesMount(hostPath, { readOnly? })` | Mount a host `node_modules` | - -## `secure-exec/typescript` - -| Export | Signature | Purpose | -|---|---|---| -| `evaluate`, `execute`, `executeFile` | As above | Types are stripped, not checked | -| `check` | `check(source, options?)` → `TypeScriptCheckResult` | Type-check without running | - -## `Vm` - -| Member | Purpose | -|---|---| -| `vm.javascript` | `execute`, `evaluate`, `executeFile`, `spawn`, `spawnFile` | -| `vm.typescript` | The same, plus `check` and `checkProject` | -| `vm.npm` | `install`, `runScript`, `runPackage` | -| `vm.filesystem` | `readFile`, `writeFile`, `readdir`, `stat`, `exists`, `mkdir`, `move`, `remove`, and more | -| `vm.network` | `httpRequest({ port, path, method?, headers?, body? })` | -| `vm.process` | `wait`, `kill`, `signal`, `list`, `get`, `readOutput`, `writeStdin`, `closeStdin` | -| `vm.createContext()` | → `Context` | -| `vm.dispose()` | Shut the VM down | - -Each namespace is the agentOS one. See [Going Further with agentOS](/secure-exec/docs/agentos). - -## `Context` - -| Member | Purpose | -|---|---| -| `context.contextId` | Pass to `vm.typescript` to share state with TypeScript | -| `context.execute(source, options?)` | Run code against the retained state | -| `context.evaluate(expression, options?)` | Evaluate against the retained state | -| `context.reset()` | Clear the state | -| `context.dispose()` | Delete the context. The VM keeps running | - -## Options - -Execution options apply to every `execute`, `evaluate`, and `executeFile`. - -| Option | Type | Purpose | -|---|---|---| -| `inputs` | JSON object | Values the code reads from `inputs` | -| `timeoutMs` | `number` | Stop the code after this long | -| `signal` | `AbortSignal` | Cancel the call | -| `output` | `{ capture?: "none" \| "stderr" \| "all" }` | Retain output on the result | -| `onStdout`, `onStderr` | `(chunk: Uint8Array) => void` | Stream output | -| `env`, `cwd`, `args`, `stdin` | | The process environment the code sees | -| `filePath` | `string` | The path used for stack traces and import resolution | -| `format` | `"module"` \| `"commonjs"` | How JavaScript source is loaded. Defaults to `"module"` | - -VM options apply to `createVm` and to the top-level one-shot functions. - -| Option | Purpose | -|---|---| -| `permissions` | [Permissions](/secure-exec/docs/permissions). External network access is denied by default | -| `limits` | [Resource limits](/secure-exec/docs/resource-limits) | -| `mounts` | [Filesystem mounts](/secure-exec/docs/filesystem) | -| `hostFunctions` | [Host functions](/secure-exec/docs/host-functions) | -| `onLimitWarning` | Called when a limit is nearly reached | - -The remaining agentOS VM options are accepted too. See the -[agentOS docs](/agentos/docs). - -## Results - -| Field | On | Purpose | -|---|---|---| -| `outcome` | All | `succeeded`, `failed`, `timed_out`, or `cancelled` | -| `value` | `evaluate`, when it succeeded | The JSON value | -| `error` | Every outcome except `succeeded` | `{ code, name, message }` | -| `stdout`, `stderr` | When captured | Output as text | -| `stdoutTruncated`, `stderrTruncated` | When captured | Output exceeded the limit | -| `exitCode` | When the code ran | The guest process exit code | -| `diagnostics`, `hasErrors` | `check` | Type errors, each with `code`, `category`, `message`, and location | - -See [Errors](/secure-exec/docs/errors) for what is returned and what is thrown. diff --git a/secure-exec/docs/content/docs/comparison/quickjs.mdx b/secure-exec/docs/content/docs/comparison/quickjs.mdx index 70ce8a4b45..4867cb483a 100644 --- a/secure-exec/docs/content/docs/comparison/quickjs.mdx +++ b/secure-exec/docs/content/docs/comparison/quickjs.mdx @@ -72,7 +72,7 @@ event loop. It has no native binary, so it runs in browsers, Deno, Bun, and edge runtimes, where Secure Exec cannot. It is small and starts almost instantly. The -WebAssembly layer contains memory-safety bugs in the engine, which keep being +WebAssembly layer confines memory-safety bugs in the engine, which keep being found, so an engine bug cannot inject code into your host. Its core language conformance is excellent. diff --git a/secure-exec/docs/content/docs/comparison/vercel-run.mdx b/secure-exec/docs/content/docs/comparison/vercel-run.mdx index 4302df951c..60354baa6d 100644 --- a/secure-exec/docs/content/docs/comparison/vercel-run.mdx +++ b/secure-exec/docs/content/docs/comparison/vercel-run.mdx @@ -68,7 +68,7 @@ data, the interpreter is what you will notice. ## What Run does well Run installs as pure JavaScript with no native binary and runs on Bun. The -WebAssembly layer contains memory-safety bugs in the engine itself. Its host +WebAssembly layer confines memory-safety bugs in the engine itself. Its host functions are well designed, and interrupt and resume for approvals is built in. ## Only choose Run when diff --git a/secure-exec/docs/content/docs/contexts.mdx b/secure-exec/docs/content/docs/contexts.mdx index ce8094f3ff..f879ae9470 100644 --- a/secure-exec/docs/content/docs/contexts.mdx +++ b/secure-exec/docs/content/docs/contexts.mdx @@ -18,8 +18,7 @@ need a context for those. Each call is its own ES module, so top-level `const` and `let` stay scoped to that call. Put values on `globalThis` to share them. A context holds memory and -does nothing between calls. See the -[execution model](/secure-exec/docs/execution-model) for the details. +does nothing between calls. ## TypeScript diff --git a/secure-exec/docs/content/docs/errors.mdx b/secure-exec/docs/content/docs/errors.mdx deleted file mode 100644 index 2e3c79238f..0000000000 --- a/secure-exec/docs/content/docs/errors.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: "Errors" -description: "Guest failures are returned as results and host mistakes are thrown as typed errors. How to handle both in Secure Exec." -skill: true ---- - -Secure Exec separates two kinds of failure, so untrusted code can never crash -your process with an exception. - -## Guest failures are returned - -Anything the guest code does wrong comes back as a result with an `outcome`. - - - -| `outcome` | `error.code` | Cause | -|---|---|---| -| `failed` | `execution_failed` | The code threw, or exited non-zero | -| `failed` | `evaluation_serialization_failed` | `evaluate` produced a value that is not JSON | -| `timed_out` | | `timeoutMs` elapsed | -| `cancelled` | | The `signal` you passed was aborted | - -The guest's own message and stack trace arrive on `stderr`. Capture it with -`output: { capture: "stderr" }` when you want to show it to a user or a model. - -## Host mistakes are thrown - -A call rejects only when the host asked for something that cannot run. - - - -| Error | When | -|---|---| -| `SidecarRejectedError` | The sidecar refused the request. `error.detail.code` says why | -| `SidecarProcessExited` | The sidecar process died | -| `SidecarSilenceTimeout` | The sidecar stopped responding | -| `KernelError` | A host-side filesystem or process operation failed, with a POSIX-style `code` | -| `TypeError` / validation error | An option is missing or malformed | - -Common `detail.code` values on `SidecarRejectedError`: - -| Code | Meaning | -|---|---| -| `execution_busy` | The context is already running a call | -| `context_not_found` | The context was disposed, or its VM was | - -Errors that hit a limit name the limit and the option that raises it. See -[Resource Limits](/secure-exec/docs/resource-limits). diff --git a/secure-exec/docs/content/docs/execute-and-evaluate.mdx b/secure-exec/docs/content/docs/execute-and-evaluate.mdx index 9a0aa271eb..659284d97e 100644 --- a/secure-exec/docs/content/docs/execute-and-evaluate.mdx +++ b/secure-exec/docs/content/docs/execute-and-evaluate.mdx @@ -23,7 +23,7 @@ Several statements go in a function: `execute` runs source as an ES module, so `import` and top-level `await` work. It returns captured output instead of a value. - + ## Run a file @@ -41,6 +41,15 @@ file under `/workspace` finds packages installed there. +## TypeScript + +`secure-exec/typescript` has the same `execute`, `evaluate`, and `executeFile` +functions for TypeScript source. They strip types rather than check them, and a +separate `check` type-checks without running anything. That is useful for +validating model-generated code before you execute it. + +See [TypeScript](/secure-exec/docs/typescript). + ## Results Both functions resolve to a result with an `outcome`. @@ -52,14 +61,48 @@ Both functions resolve to a result with an `outcome`. | `timed_out` | `timeoutMs` elapsed. | | `cancelled` | The `signal` you passed was aborted. | -Every outcome other than `succeeded` carries an `error` with a `code` and -`message`. The guest's own stack trace arrives on `stderr`, so capture it when -you want to show it to a user or a model. - -Guest failures are never thrown. A call rejects only when the host cannot run -it at all, such as an invalid option. +## Guest failures are returned + +Anything the guest code does wrong comes back as a result, never as an +exception, so untrusted code can never crash your process. + + + +| `outcome` | `error.code` | Cause | +|---|---|---| +| `failed` | `execution_failed` | The code threw, or exited non-zero | +| `failed` | `evaluation_serialization_failed` | `evaluate` produced a value that is not JSON | +| `timed_out` | | `timeoutMs` elapsed | +| `cancelled` | | The `signal` you passed was aborted | + +The guest's own message and stack trace arrive on `stderr`. Capture it with +`output: { capture: "stderr" }` when you want to show it to a user or a model. + +## Host mistakes are thrown + +A call rejects only when the host asked for something that cannot run. + + + +| Error | When | +|---|---| +| `SidecarRejectedError` | The sidecar refused the request. `error.detail.code` says why | +| `SidecarProcessExited` | The sidecar process died | +| `SidecarSilenceTimeout` | The sidecar stopped responding | +| `KernelError` | A host-side filesystem or process operation failed, with a POSIX-style `code` | +| `TypeError` / validation error | An option is missing or malformed | + +Common `detail.code` values on `SidecarRejectedError`: + +| Code | Meaning | +|---|---| +| `execution_busy` | The context is already running a call | +| `context_not_found` | The context was disposed, or its VM was | + +Errors that hit a limit name the limit and the option that raises it. See +[Resource Limits](/secure-exec/docs/resource-limits). ## Options diff --git a/secure-exec/docs/content/docs/execution-model.mdx b/secure-exec/docs/content/docs/execution-model.mdx deleted file mode 100644 index fe639802af..0000000000 --- a/secure-exec/docs/content/docs/execution-model.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: "Execution Model" -description: "How Secure Exec runs a call: what is isolated at each level, when a call finishes, and how state and processes relate." -skill: true ---- - -You do not need this page to use Secure Exec. It explains the behavior behind -[VMs](/secure-exec/docs/vms), [contexts](/secure-exec/docs/contexts), and -[long-running code](/secure-exec/docs/long-running-code). - -## Three levels of isolation - -| You call | Runs in | Shares with other calls | -|---|---|---| -| `evaluate(code)` | A fresh VM, disposed afterwards | Nothing | -| `vm.javascript.evaluate(code)` | Fresh JavaScript memory in that VM | Files, packages, network, limits | -| `context.evaluate(code)` | That context's JavaScript memory | The above, plus variables and imports | - -Every VM runs inside one sidecar process, separate from your own. A VM has its -own virtual kernel: filesystem, process table, sockets, and permission policy. - -## When a call finishes - -`execute` and `evaluate` run your code the way `node -e` would. A call finishes -when the code's **event loop drains**, not when its last line runs. An un-awaited -promise or a pending timer keeps the call open until it settles. A server or an -interval that never ends keeps it open until `timeoutMs`, and the call returns -`timed_out`. - -Each call is its own ES module. Top-level `const` and `let` are scoped to that -call, and `import` and top-level `await` work. - -## Contexts are idle between calls - -A context holds JavaScript memory. It does not keep running. - -- Nothing executes between calls. A timer or promise left over from a finished - call never fires, even during a later call. -- A context runs one call at a time. A second call while one is running rejects - with `execution_busy`. -- A call that times out clears the context's state. - -For work that should run at the same time, create several contexts in one VM. For -work that should keep running, use a process. - -## Processes keep running - -`vm.javascript.spawn` starts a program that lives until it exits or you stop it, -like a process on Linux. Any number can run at once, alongside your calls. A -process shares the VM's filesystem, packages, and network with everything else in -the VM, and shares JavaScript memory with nothing. diff --git a/secure-exec/docs/content/docs/host-functions.mdx b/secure-exec/docs/content/docs/host-functions.mdx index 789cdc1254..8b2585b9ce 100644 --- a/secure-exec/docs/content/docs/host-functions.mdx +++ b/secure-exec/docs/content/docs/host-functions.mdx @@ -7,7 +7,7 @@ skill: true Untrusted code should never hold your database password or API key. A host function runs in your process with your credentials, and the guest only sees its inputs and outputs. This is what makes -[code mode](/secure-exec/docs/use-cases/ai-agent-code-exec) useful: the model +[Code Mode](/secure-exec/docs/use-cases/code-mode) useful: the model writes one program that chains your tools. ## Define host functions @@ -39,5 +39,5 @@ Inside the VM each collection is a global object and each function is async. The same functions are also available to shell scripts and other languages as commands. Read the [agentOS host functions -reference](/agentos/docs/host-functions) for that, -and for schemas, timeouts, and examples. +reference](/agentos/docs/host-functions) for that, and for schemas, timeouts, +and examples. diff --git a/secure-exec/docs/content/docs/index.mdx b/secure-exec/docs/content/docs/index.mdx index b01e4b77fc..413229e4c7 100644 --- a/secure-exec/docs/content/docs/index.mdx +++ b/secure-exec/docs/content/docs/index.mdx @@ -1,13 +1,9 @@ --- title: "Introduction" -description: "Secure Exec runs untrusted JavaScript and TypeScript in an isolated VM with real Node.js APIs, npm packages, and a virtual filesystem." +description: "Secure Exec is a library for running code you do not trust. Give an AI agent, a plugin author, or a user the ability to write and run code, without giving them your host." skill: true --- -Secure Exec is a library for running code you do not trust. Give an AI agent, -a plugin author, or a user the ability to write and run code, without giving -them your host. - - **One function call.** `evaluate` and `execute` are all you need. There is no @@ -15,8 +11,6 @@ them your host. - **Real Node.js inside.** Guest code runs on V8 with `node:fs`, `node:child_process`, sockets, and npm packages. It is not a stripped-down interpreter. -- **A fresh VM per call.** Nothing is shared between calls unless you opt in - by creating a [VM](/secure-exec/docs/vms). - **Deny by default.** The guest sees a virtual filesystem and no network until you [allow it](/secure-exec/docs/permissions). - **No infrastructure.** `npm install secure-exec`. No containers, no vendor @@ -25,25 +19,5 @@ them your host. ## Where to start - [Quickstart](/secure-exec/docs/quickstart) installs the package and runs your first snippet. -- [AI Agent Code Exec](/secure-exec/docs/use-cases/ai-agent-code-exec) wires it into an agent tool. +- [Agent Code Tool](/secure-exec/docs/use-cases/agent-code-tool) wires it into an agent tool. - [Security](/secure-exec/docs/security) covers what is isolated and what is denied by default. - -## The API at a glance - -There are two levels: one-shot functions, and a VM for anything that persists. - -| | | -|---|---| -| `secure-exec` | `evaluate`, `execute`, `executeFile`, `createVm`, `init`, `shutdown` | -| `secure-exec/typescript` | `evaluate`, `execute`, `executeFile`, `check` | -| A [VM](/secure-exec/docs/vms) from `createVm()` | `vm.javascript`, `vm.typescript`, `vm.npm`, `vm.filesystem`, `vm.network`, `vm.process`, `vm.createContext()` | - -## Built on agentOS - -Secure Exec is a small function-style API over [agentOS](/agentos/docs), the -same VM that runs full coding agents. When you need processes, a shell, or -agent sessions, [use agentOS directly](/secure-exec/docs/agentos). - -Looking for Python? Secure Exec covers JavaScript and TypeScript. The -[agentOS Python execution API](/agentos/docs/python) runs Python in the same VM -with the same `execute` and `evaluate` shape. diff --git a/secure-exec/docs/content/docs/limitations.mdx b/secure-exec/docs/content/docs/limitations.mdx deleted file mode 100644 index a11e8f72be..0000000000 --- a/secure-exec/docs/content/docs/limitations.mdx +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: "Limitations" -description: "Known limitations and rough edges in Secure Exec, with workarounds." -skill: true ---- - -## Platform - -- Linux with glibc and macOS only. See [Platforms & Install](/secure-exec/docs/platforms). -- JavaScript and TypeScript only. For Python, use the - [agentOS Python API](/agentos/docs/python). -- `await using` needs TypeScript 5.2 or newer, or Node.js 24 for plain - JavaScript. Otherwise call `dispose()` in a `finally` block. - -## Calls and contexts - -- **A leftover handle fails the call.** A call finishes when the event loop - drains, so a stray interval or open socket turns a finished snippet into - `timed_out`, and in a context that clears its state. Clear timers and close - what you open. Tracked in [#1991](https://github.com/rivet-dev/agentos/issues/1991). -- **Contexts do nothing between calls**, and run one call at a time. See the - [execution model](/secure-exec/docs/execution-model). -- **Aborting a call is noisy.** Cancelling through `signal` returns `cancelled` - as it should, but the sidecar logs an internal error and the VM is slow to - dispose afterwards. Prefer `timeoutMs`. Tracked in - [#1988](https://github.com/rivet-dev/agentos/issues/1988). - -## Modules - -- **Inline code resolves `import()` from `/`.** A package installed in - `/workspace` is not found by inline `execute` or `evaluate` code. Run a file - with `executeFile`, or pass `filePath: "/workspace/main.mjs"` to the inline - call. Tracked in [#1990](https://github.com/rivet-dev/agentos/issues/1990). - -## Network - -- **Dispose is slow after network use.** A VM that used the network takes - several seconds longer to dispose, and much longer if a response body was left - unread. Always read or cancel response bodies. Tracked in - [#1989](https://github.com/rivet-dev/agentos/issues/1989). -- **Host patterns need a scheme.** Allowing a host takes both - `dns://` and `tcp://:*`. A bare hostname matches nothing. See - [permissions](/secure-exec/docs/permissions). - -## Host functions - -- Each call starts a small helper process inside the VM, which costs a few - milliseconds. Batch work into fewer calls when it matters, or run calls in - parallel with `Promise.all`. diff --git a/secure-exec/docs/content/docs/long-running-code.mdx b/secure-exec/docs/content/docs/long-running-code.mdx index 1bffe51b0b..1f4a934e40 100644 --- a/secure-exec/docs/content/docs/long-running-code.mdx +++ b/secure-exec/docs/content/docs/long-running-code.mdx @@ -1,13 +1,12 @@ --- -title: "Long-Running Code" +title: "Long-Running Processes" description: "Run servers, watchers, and other background processes in Secure Exec with spawn, and reach them from the host." skill: true --- `execute` and `evaluate` wait for your code to finish, so a server started that way runs until `timeoutMs` and returns `timed_out`. For code that is meant to -keep running, `spawn` it in a [VM](/secure-exec/docs/vms). The -[execution model](/secure-exec/docs/execution-model) explains why. +keep running, `spawn` it in a [VM](/secure-exec/docs/vms). ## Spawn a background process @@ -46,7 +45,9 @@ exposed on the host's own network. `wait(pid)` resolves when the process exits, `kill(pid)` and `signal(pid, signal)` -stop it, and `list()` shows what is running. `onStdout` and `onStderr` on `spawn` stream its output live. Stop spawned processes before you dispose the VM. +stop it, and `list()` shows what is running. `onStdout` and `onStderr` on +`spawn` stream its output live. Stop spawned processes before you dispose the +VM. Read the [agentOS processes docs](/agentos/docs/processes) for the full process model. diff --git a/secure-exec/docs/content/docs/networking.mdx b/secure-exec/docs/content/docs/networking.mdx index 014947361f..a6f1b1c1bd 100644 --- a/secure-exec/docs/content/docs/networking.mdx +++ b/secure-exec/docs/content/docs/networking.mdx @@ -11,10 +11,8 @@ network stack inside the VM. No guest code ever holds a real host socket. published as host ports. - External network access is **denied by default**. Grant it, or specific hosts, with [permissions](/secure-exec/docs/permissions). -- Read or cancel response bodies you request. See - [limitations](/secure-exec/docs/limitations). - To call a server running in the guest, use `vm.network.httpRequest`. See - [Long-Running Code](/secure-exec/docs/long-running-code). + [Long-Running Processes](/secure-exec/docs/long-running-code). Read the [agentOS networking docs](/agentos/docs/networking) for how the stack works and what it supports. diff --git a/secure-exec/docs/content/docs/npm.mdx b/secure-exec/docs/content/docs/npm.mdx index 0d314d21ca..e2afec3730 100644 --- a/secure-exec/docs/content/docs/npm.mdx +++ b/secure-exec/docs/content/docs/npm.mdx @@ -15,8 +15,8 @@ that outlives the call. - Installing needs the network, which is denied until you allow it. - Packages install into the working directory, `/workspace`. Run your code from a - file there so it finds them. Inline code has a - [limitation](/secure-exec/docs/limitations) here. + file there so it finds them, or set `filePath` to a path inside `/workspace` + when inline code imports an installed package. - `vm.npm.install()` with no package list installs the dependencies of the `package.json` in the working directory. - `vm.npm.runScript` and `vm.npm.runPackage` work like `npm run` and `npx`. diff --git a/secure-exec/docs/content/docs/platforms.mdx b/secure-exec/docs/content/docs/platforms.mdx deleted file mode 100644 index 82476ccd44..0000000000 --- a/secure-exec/docs/content/docs/platforms.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: "Platforms & Install" -description: "Supported operating systems, Node.js versions, and how the Secure Exec native sidecar is installed." -skill: true ---- - -```sh -npm install secure-exec -``` - -Secure Exec runs guest code in a native sidecar binary. The right binary for your -platform installs automatically as an optional dependency, so there is nothing -to compile. - -## Supported platforms - -| Platform | Supported | -|---|---| -| Linux x64 and arm64, glibc | Yes | -| macOS x64 and arm64 | Yes | -| Linux with musl, such as Alpine | No. Use a glibc image such as `node:22-slim` | -| Windows | No. Use WSL 2 | -| Browsers and edge runtimes | No | - -Secure Exec requires Node.js 22 or newer. - -## Docker and CI - -- Use a glibc base image. Debian and Ubuntu based Node.js images work as they are. -- Install with optional dependencies enabled, which is the default. With - `--no-optional` or `--omit=optional` the sidecar binary is skipped and the - first call fails. -- The sidecar needs no privileges, no kernel features, and no network access of - its own. - -## Use a specific binary - -Set `AGENTOS_SIDECAR_BIN` to the absolute path of a sidecar binary to use it -instead of the installed one. diff --git a/secure-exec/docs/content/docs/quickstart.mdx b/secure-exec/docs/content/docs/quickstart.mdx index 72c251e403..64df2adfd9 100644 --- a/secure-exec/docs/content/docs/quickstart.mdx +++ b/secure-exec/docs/content/docs/quickstart.mdx @@ -12,8 +12,7 @@ skill: true npm install secure-exec ``` -Secure Exec requires Node.js 22 or newer on Linux or macOS. See -[Platforms & Install](/secure-exec/docs/platforms). +Secure Exec requires Node.js 22 or newer on Linux (glibc) or macOS. @@ -31,11 +30,13 @@ Secure Exec requires Node.js 22 or newer on Linux or macOS. See - + -`execute` runs a whole ES module for its side effects. +Host functions run in your process, with your credentials. Each collection is a +global inside the VM whose methods are async, so the guest calls them like +ordinary functions. Nothing else about your process crosses into the VM. - + @@ -52,6 +53,7 @@ process. ## Next steps +- [Host Functions](/secure-exec/docs/host-functions) covers schemas, errors, and Code Mode. - [VMs](/secure-exec/docs/vms) keep files, packages, and processes across calls. - [Execute & Evaluate](/secure-exec/docs/execute-and-evaluate) explains results and options in detail. - [Permissions](/secure-exec/docs/permissions) shows how to grant the network. diff --git a/secure-exec/docs/content/docs/security.mdx b/secure-exec/docs/content/docs/security.mdx index f557bf8e01..bfe90e05fe 100644 --- a/secure-exec/docs/content/docs/security.mdx +++ b/secure-exec/docs/content/docs/security.mdx @@ -1,5 +1,5 @@ --- -title: "Security" +title: "Overview" description: "What Secure Exec isolates, what it denies by default, and what remains your responsibility when running untrusted code." skill: true --- @@ -27,8 +27,6 @@ before anything happens. ## Your responsibilities - **Set `timeoutMs` on every call** that runs code you did not write. -- **Give each tenant its own VM.** Everything in one VM shares a filesystem and a - network. - **Treat [host functions](/secure-exec/docs/host-functions) as your API surface.** They run with your credentials, so validate and authorize inside them. @@ -36,4 +34,4 @@ before anything happens. - **Grant hosts, not the whole network,** when the code only needs one API. Read the [agentOS security model](/agentos/docs/security-model) for the full -trust model, what is guaranteed, and the known [limitations](/secure-exec/docs/limitations). +trust model and what is guaranteed. diff --git a/secure-exec/docs/content/docs/typescript.mdx b/secure-exec/docs/content/docs/typescript.mdx index 57306f6f1b..043764ebf7 100644 --- a/secure-exec/docs/content/docs/typescript.mdx +++ b/secure-exec/docs/content/docs/typescript.mdx @@ -31,4 +31,4 @@ the working directory using its `tsconfig.json`, and `vm.typescript` has the same `execute`, `evaluate`, `executeFile`, and `check` as this entry point. Feeding diagnostics back to a model is the cheapest way to fix generated code. -See [AI Agent Code Exec](/secure-exec/docs/use-cases/ai-agent-code-exec). +See [Agent Code Tool](/secure-exec/docs/use-cases/agent-code-tool). diff --git a/secure-exec/docs/content/docs/use-cases/ai-agent-code-exec.mdx b/secure-exec/docs/content/docs/use-cases/agent-code-tool.mdx similarity index 98% rename from secure-exec/docs/content/docs/use-cases/ai-agent-code-exec.mdx rename to secure-exec/docs/content/docs/use-cases/agent-code-tool.mdx index c375eae5d4..037273bb2f 100644 --- a/secure-exec/docs/content/docs/use-cases/ai-agent-code-exec.mdx +++ b/secure-exec/docs/content/docs/use-cases/agent-code-tool.mdx @@ -1,5 +1,5 @@ --- -title: "AI Agent Code Exec" +title: "Agent Code Tool" description: "Give an AI agent a code-execution tool that runs model-generated code in an isolated VM and returns structured results." skill: true --- diff --git a/secure-exec/docs/content/docs/use-cases/code-mode.mdx b/secure-exec/docs/content/docs/use-cases/code-mode.mdx new file mode 100644 index 0000000000..eee7ee1617 --- /dev/null +++ b/secure-exec/docs/content/docs/use-cases/code-mode.mdx @@ -0,0 +1,37 @@ +--- +title: "Code Mode" +description: "Give an agent one execution tool over curated host functions instead of exposing every tool directly." +skill: true +--- + +Code Mode gives a model **one** tool instead of one tool per capability. The +program it writes can branch, loop, transform data, and call several narrow host +functions in a single isolated VM. A task that would have been six round trips +becomes one. + +## Register host capabilities + + + +The handlers run on the host, but only their validated inputs and JSON results +cross into the VM. The guest never receives the credentials or the direct +resource access a handler uses. + +## Generate one program + + + +## Evaluate it + + + +Expose `evaluate` as the agent's single code-execution tool. +[Permissions](/secure-exec/docs/permissions), +[resource limits](/secure-exec/docs/resource-limits), and `timeoutMs` apply to +every program the model writes. + +## Next steps + +- [Host Functions](/secure-exec/docs/host-functions) covers schemas and errors. +- [Agent Code Tool](/secure-exec/docs/use-cases/agent-code-tool) type-checks + generated code before running it. diff --git a/secure-exec/docs/content/docs/use-cases/dev-servers.mdx b/secure-exec/docs/content/docs/use-cases/dev-servers.mdx index 5bdebdf3b4..a0c272cc3f 100644 --- a/secure-exec/docs/content/docs/use-cases/dev-servers.mdx +++ b/secure-exec/docs/content/docs/use-cases/dev-servers.mdx @@ -20,8 +20,8 @@ Forward requests from your own route to the guest, and return what it answers. - **No host port.** The server listens on the VM's virtual network. Only your process can reach it, through `vm.network.httpRequest`. -- **One VM per preview.** Each tenant's server gets its own VM, filesystem, - and [limits](/secure-exec/docs/resource-limits). +- **One VM per preview.** Each preview gets its own filesystem and + [limits](/secure-exec/docs/resource-limits). - **Bring a project.** Write files with `vm.filesystem`, install dependencies with [npm](/secure-exec/docs/npm), then spawn the entry point. @@ -29,4 +29,4 @@ Forward requests from your own route to the guest, and return what it answers. -Read more in [Long-Running Code](/secure-exec/docs/long-running-code). +Read more in [Long-Running Processes](/secure-exec/docs/long-running-code). diff --git a/secure-exec/docs/content/docs/vms.mdx b/secure-exec/docs/content/docs/vms.mdx index 48de945467..571b35ceb1 100644 --- a/secure-exec/docs/content/docs/vms.mdx +++ b/secure-exec/docs/content/docs/vms.mdx @@ -26,7 +26,7 @@ create the VM yourself. | `vm.createContext()` | JavaScript state that survives between calls | | `vm.dispose()` | Shut the VM down | -A Secure Exec VM **is** an [agentOS](/secure-exec/docs/agentos) VM with Secure +A Secure Exec VM **is** an [agentOS](https://rivet.dev/agentos) VM with Secure Exec's defaults, so each namespace behaves exactly as the agentOS docs describe. ## Files across calls @@ -49,9 +49,6 @@ A VM holds memory until you dispose it. Use `try` and `finally`, or -Give each tenant its own VM. Everything in one VM shares a filesystem, a network, -and one set of limits. - ## What you usually do not need Most stateful work needs a VM and nothing else. Files, packages, and servers all diff --git a/secure-exec/docs/sidebar.json b/secure-exec/docs/sidebar.json index 5b61bb25ac..3d26792748 100644 --- a/secure-exec/docs/sidebar.json +++ b/secure-exec/docs/sidebar.json @@ -12,11 +12,6 @@ "title": "Quickstart", "href": "/secure-exec/docs/quickstart", "icon": "faForwardFast" - }, - { - "title": "Platforms & Install", - "href": "/secure-exec/docs/platforms", - "icon": "faDownload" } ] }, @@ -24,10 +19,15 @@ "title": "Use Cases", "pages": [ { - "title": "AI Agent Code Exec", - "href": "/secure-exec/docs/use-cases/ai-agent-code-exec", + "title": "Agent Code Tool", + "href": "/secure-exec/docs/use-cases/agent-code-tool", "icon": "faRobot" }, + { + "title": "Code Mode", + "href": "/secure-exec/docs/use-cases/code-mode", + "icon": "faCode" + }, { "title": "Plugin Systems", "href": "/secure-exec/docs/use-cases/plugin-systems", @@ -41,102 +41,64 @@ ] }, { - "title": "Running Code", + "title": "Executing Code", "pages": [ { "title": "Execute & Evaluate", - "href": "/secure-exec/docs/execute-and-evaluate", - "icon": "faTerminal" + "href": "/secure-exec/docs/execute-and-evaluate" }, { - "title": "Output Capture", - "href": "/secure-exec/docs/output-capture", - "icon": "faMessages" + "title": "Host Functions", + "href": "/secure-exec/docs/host-functions" + }, + { + "title": "TypeScript", + "href": "/secure-exec/docs/typescript" }, { "title": "VMs", - "href": "/secure-exec/docs/vms", - "icon": "faHardDrive" + "href": "/secure-exec/docs/vms" }, { - "title": "Long-Running Code", - "href": "/secure-exec/docs/long-running-code", - "icon": "faClock" + "title": "Contexts", + "href": "/secure-exec/docs/contexts" }, { - "title": "TypeScript", - "href": "/secure-exec/docs/typescript", - "icon": "faCheck" + "title": "Long-Running Processes", + "href": "/secure-exec/docs/long-running-code" }, { "title": "npm Packages", - "href": "/secure-exec/docs/npm", - "icon": "faNodeJs" + "href": "/secure-exec/docs/npm" }, { - "title": "Host Functions", - "href": "/secure-exec/docs/host-functions", - "icon": "faWrench" + "title": "Output Capture", + "href": "/secure-exec/docs/output-capture" } ] }, { - "title": "Isolation", + "title": "Security", "pages": [ { - "title": "Security", - "href": "/secure-exec/docs/security", - "icon": "faCheck" + "title": "Overview", + "href": "/secure-exec/docs/security" }, { "title": "Permissions", - "href": "/secure-exec/docs/permissions", - "icon": "faKey" + "href": "/secure-exec/docs/permissions" }, { "title": "Resource Limits", - "href": "/secure-exec/docs/resource-limits", - "icon": "faGauge" + "href": "/secure-exec/docs/resource-limits" }, { "title": "Filesystem & Mounts", - "href": "/secure-exec/docs/filesystem", - "icon": "faFloppyDisk" + "href": "/secure-exec/docs/filesystem" }, { "title": "Networking", - "href": "/secure-exec/docs/networking", - "icon": "faGlobe" - } - ] - }, - { - "title": "Advanced", - "pages": [ - { - "title": "Contexts", - "href": "/secure-exec/docs/contexts", - "icon": "faLayerGroup" - }, - { - "title": "Execution Model", - "href": "/secure-exec/docs/execution-model", - "icon": "faDiagramNext" - }, - { - "title": "Lifecycle", - "href": "/secure-exec/docs/lifecycle", - "icon": "faBolt" - }, - { - "title": "Errors", - "href": "/secure-exec/docs/errors", - "icon": "faMessages" - }, - { - "title": "Limitations", - "href": "/secure-exec/docs/limitations", - "icon": "faClock" + "href": "/secure-exec/docs/networking" } ] }, @@ -144,55 +106,12 @@ "title": "Reference", "pages": [ { - "title": "API Reference", - "href": "/secure-exec/docs/api-reference", - "icon": "faWindowMaximize" - }, - { - "title": "Going Further with agentOS", - "href": "/secure-exec/docs/agentos", - "icon": "faArrowsLeftRight" + "title": "Lifecycle", + "href": "/secure-exec/docs/lifecycle" }, { "title": "Benchmarks", - "href": "/secure-exec/docs/benchmarks", - "icon": "faGauge" - }, - { - "title": "Python", - "href": "/agentos/docs/python", - "icon": "faPython" - }, - { - "title": "Comparison", - "collapsible": true, - "icon": "faScaleBalanced", - "pages": [ - { - "title": "vs Vercel Run", - "href": "/secure-exec/docs/comparison/vercel-run" - }, - { - "title": "vs QuickJS", - "href": "/secure-exec/docs/comparison/quickjs" - }, - { - "title": "vs secure-eval-worker", - "href": "/secure-exec/docs/comparison/secure-eval-worker" - }, - { - "title": "vs isolated-vm", - "href": "/secure-exec/docs/comparison/isolated-vm" - }, - { - "title": "vs Cloudflare Workers", - "href": "/secure-exec/docs/comparison/cloudflare-workers" - }, - { - "title": "vs Container Sandboxes", - "href": "/secure-exec/docs/comparison/container-sandboxes" - } - ] + "href": "/secure-exec/docs/benchmarks" } ] } diff --git a/secure-exec/examples/code-mode/package.json b/secure-exec/examples/code-mode/package.json new file mode 100644 index 0000000000..b2d3648800 --- /dev/null +++ b/secure-exec/examples/code-mode/package.json @@ -0,0 +1,18 @@ +{ + "name": "@rivet-dev/agentos-example-secure-exec-code-mode", + "private": true, + "type": "module", + "scripts": { + "start": "tsx src/index.ts", + "check-types": "tsc --noEmit" + }, + "dependencies": { + "secure-exec": "workspace:*", + "zod": "^4.1.11" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "tsx": "^4.19.2", + "typescript": "^5.7.2" + } +} diff --git a/secure-exec/examples/code-mode/src/index.ts b/secure-exec/examples/code-mode/src/index.ts new file mode 100644 index 0000000000..1e7b0941ad --- /dev/null +++ b/secure-exec/examples/code-mode/src/index.ts @@ -0,0 +1,57 @@ +import { evaluate } from "secure-exec"; +import { z } from "zod"; + +// docs:start host-functions +// Each narrow capability the agent is allowed to use. The handlers run on the +// host with your credentials; the guest only ever sees validated inputs and +// JSON results. `.describe()` on the schema is what the model reads. +const store = { + listOrders: { + inputSchema: z + .object({ customer: z.string() }) + .describe("List a customer's orders."), + execute: ({ customer }: { customer: string }) => [ + { id: "o_1", customer, total: 40, status: "shipped" }, + { id: "o_2", customer, total: 2, status: "pending" }, + ], + }, + refund: { + inputSchema: z + .object({ orderId: z.string() }) + .describe("Refund one order."), + execute: ({ orderId }: { orderId: string }) => ({ + orderId, + refunded: true, + }), + }, +}; +// docs:end host-functions + +// docs:start generated-code +// One program from the model instead of a tool call per step. It branches, +// loops, and combines results before anything comes back to you. +const generated = ` + const orders = await store.listOrders({ customer: inputs.customer }); + const pending = orders.filter((order) => order.status === "pending"); + for (const order of pending) { + await store.refund({ orderId: order.id }); + } + return { refunded: pending.length, total: pending.reduce((sum, o) => sum + o.total, 0) }; +`; +// docs:end generated-code + +// docs:start run +// Expose this as the agent's single code-execution tool. Permissions, limits, +// and the timeout apply to every program it writes. +const result = await evaluate<{ refunded: number; total: number }>( + `(async () => {${generated}})()`, + { + hostFunctions: { store }, + inputs: { customer: "customer_123" }, + timeoutMs: 5_000, + output: { capture: "stderr" }, + }, +); +console.log(result.outcome === "succeeded" ? result.value : result.stderr); +// { refunded: 1, total: 2 } +// docs:end run diff --git a/secure-exec/examples/code-mode/tsconfig.json b/secure-exec/examples/code-mode/tsconfig.json new file mode 100644 index 0000000000..5ead5b22da --- /dev/null +++ b/secure-exec/examples/code-mode/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} diff --git a/secure-exec/examples/host-functions/src/index.ts b/secure-exec/examples/host-functions/src/index.ts index 48aaab934c..8f3bf343f5 100644 --- a/secure-exec/examples/host-functions/src/index.ts +++ b/secure-exec/examples/host-functions/src/index.ts @@ -1,6 +1,7 @@ import { evaluate } from "secure-exec"; import { z } from "zod"; +// docs:start quickstart // docs:start call // Inside the VM each collection is a global, and each function is async. This // is the code a model would write. @@ -34,3 +35,4 @@ const total = await evaluate(generated, { }); console.log(total.outcome === "succeeded" ? total.value : total.stderr); // 42 // docs:end define +// docs:end quickstart diff --git a/secure-exec/examples/quickstart/src/index.ts b/secure-exec/examples/quickstart/src/index.ts index 93393b5f08..a3a79d005a 100644 --- a/secure-exec/examples/quickstart/src/index.ts +++ b/secure-exec/examples/quickstart/src/index.ts @@ -1,5 +1,5 @@ // docs:start evaluate -import { evaluate, execute } from "secure-exec"; +import { evaluate } from "secure-exec"; // Each call runs in a fresh VM that is disposed when the call finishes. const sum = await evaluate("1 + 2"); @@ -27,19 +27,6 @@ const report = await evaluate<{ count: number; max: number }>(` console.log(report.outcome === "succeeded" ? report.value : report.error); // docs:end statements -// docs:start execute -// `execute` runs a whole ES module for its side effects. Capture output to read -// it back. -const run = await execute( - ` - import { platform } from "node:os"; - console.log("running on", platform()); - `, - { output: { capture: "all" } }, -); -console.log(run.stdout?.trim()); // running on linux -// docs:end execute - // docs:start errors // Guest errors are returned, not thrown. Capture stderr to see the stack. const failed = await evaluate(`JSON.parse("not json")`, { diff --git a/secure-exec/examples/quickstart/src/module.ts b/secure-exec/examples/quickstart/src/module.ts new file mode 100644 index 0000000000..dbae04d3d3 --- /dev/null +++ b/secure-exec/examples/quickstart/src/module.ts @@ -0,0 +1,14 @@ +import { execute } from "secure-exec"; + +// docs:start execute +// `execute` runs a whole ES module for its side effects. Capture output to read +// it back. +const run = await execute( + ` + import { platform } from "node:os"; + console.log("running on", platform()); + `, + { output: { capture: "all" } }, +); +console.log(run.stdout?.trim()); // running on linux +// docs:end execute