agentleFS
rivet-devrivet-dev/agentos/crates/CLAUDE.md

agentos / crates

CLAUDE.md4.9k starsChanged 45 days agoApache-2.0View on GitHub

The Rust sidecar implements the kernel: virtual filesystem, process table, socket table, pipe/PTY management, and permission system. Guest code sees a self-contained OS and must never interact with the host directly. The kernel orchestrates three execution environments: ALL guest code MUST execute inside the kernel with ZERO host escapes. No runtime may spawn unsandboxed host processes, touch real host filesystems, open real network sockets, or call real Node.js builtins. Every syscall goes through the kernel. This is the single most…

  • Reads credentials

2 other projects use a version of this.

Trending

Steady talk.thin dataabout the whole repository

Dec 2025: 0 mentionsJan 2026: 0 mentionsFeb 2026: 0 mentionsMar 2026: 0 mentionsApr 2026: 0 mentionsMay 2026: 0 mentionsJun 2026: 2 mentionsJul 2026: 2 mentionsAug 2026: 0 mentionsSep 2026: 1 mentionOct 2026: 2 mentionsNov 2026: 1 mention
Dec 2025Jun 2026Nov 2026
Mentions a month of the rivet-dev/agentos repository that our weekly crawl found: only 8 in all, so this is a hint, not a trend. There is no adoption data for this file yet.

Most recently taken up by

Who's talking about rivet-dev/agentos

The biggest 5 of 8 mentions linking to the repository that our crawl found. Each line is their own headline.

See all 8

Talked about on GitHub and Bluesky (8 times).

On GitHub · 6

1 more on GitHub
  • Other versions2 projects use a version of this.

    Word for word · 2

    1. rivet-dev/agent-os★ 4.9k100% same
    2. rivet-dev/rivetkit★ 4.9k100% same

    Edited · 0

    None found.

    Wrote their own version · 0

    None found.

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository8 other files it gives its agents.

The file

We split this CLAUDE.md into the rules it gives an agent

4 rules. About 0 are common, 0 are distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Keep crates/sidecar/tests/builtin_conformance.rs as one top-level builtin_conformance_cases test that forks per-case subprocesses via …

    line 124
  2. Very specific:

    Run scoped tests: cargo test -p agentos-sidecar -- test_name_filter or cargo test -p agentos-sidecar --lib (lib unit tests only, skip …

    line 126
  3. Very specific:

    Never run bare cargo test -p agentos-sidecar without a filter -- integration tests spawn real processes and can hang.

    line 127
  4. Very specific:

    Do not hide first-party Rust coverage behind #[ignore].

    line 131

≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

agentOS Rust Crates

The Rust sidecar implements the kernel: virtual filesystem, process table, socket table, pipe/PTY management, and permission system. Guest code sees a self-contained OS and must never interact with the host directly.

The kernel orchestrates three execution environments:

  • WASM processes -- POSIX utilities (coreutils, sh, grep, etc.) compiled to WebAssembly, running within the sidecar's managed runtime.
  • Node.js (V8 isolates) -- JS/TS executes inside isolated V8 contexts managed by the sidecar's execution engine. All Node.js builtin modules (fs, net, child_process, dns, http, os, etc.) are kernel-backed polyfills that route through the kernel VFS, socket table, and process table via synchronous RPC. Module loading is intercepted so guest code never touches real host APIs. ⚠️ CURRENTLY BROKEN: The execution engine currently spawns real host node child processes instead of V8 isolates, and most builtins fall through to real host modules. This is being actively fixed. The complete working polyfill code from the original @agentos packages has been recovered to ~/.agents/recovery/agentos/ (source repo: /home/nathan/agentos-1/). See crates/execution/CLAUDE.md for the gap table and recovery file list.
  • Python (Pyodide) -- CPython compiled to WASM via Pyodide, with kernel-backed file/network I/O.

ALL guest code MUST execute inside the kernel with ZERO host escapes. No runtime may spawn unsandboxed host processes, touch real host filesystems, open real network sockets, or call real Node.js builtins. Every syscall goes through the kernel. This is the single most important architectural invariant. Any path where guest code reaches the real host is a critical security violation.

NEVER use Command::new("node") for guest code execution. Guest JavaScript runs inside V8 isolates (crates/v8-runtime/), not real Node.js processes. Do NOT add execution mode flags, host fallback paths, or "legacy node mode" — even temporarily, even behind a feature flag. If tests fail because they were written for the old host-process model, fix or delete the tests.

Virtualization Invariants

These are hard rules with no exceptions:

  1. Every guest syscall goes through the kernel. File reads go through the kernel VFS, not real node:fs. Network connections go through the kernel socket table, not real node:net. Process spawning go through the kernel process table, not real node:child_process. DNS resolution goes through the kernel's DNS resolver, not real node:dns. There are no shortcuts where guest code touches host resources directly.
  2. No real host builtins. When a guest does require('fs') or import net from 'node:net', the module loader must return a kernel-backed polyfill. If no polyfill exists yet, the builtin must be denied (ERR_ACCESS_DENIED). The loader must never fall through to Node.js's default resolution and hand the guest the real host module.
  3. The host is an implementation detail. Guest code should not be able to observe that it is running on a host Node.js process. process.pid should be the kernel PID, os.hostname() should be the kernel hostname, fs.readdirSync('/') should show the kernel VFS root. process.cwd() should return the kernel CWD, not a host path. process.env must not contain internal AGENT_OS_* control variables. Error messages and stack traces must not reveal host filesystem paths. require.resolve() must return guest-visible paths, not host paths. Any host state leaking through to the guest is a bug.
  4. Polyfills are ports, not wrappers. A path-translating shim over real node:fs is not a polyfill -- it is a wrapper around a host API. A real polyfill implements the API semantics using only kernel primitives (VFS, socket table, process table, pipe manager). The original JavaScript kernel packages, deleted in commit 5a43882, had full kernel-backed polyfills for fs, net, http, dns, dgram, child_process, and os. The Rust sidecar must reach the same level of isolation.
  5. Control channels must be out-of-band. The sidecar must not use in-band magic prefixes on stdout/stderr for control signaling (exit codes, metrics, signal registration). Guest code can write these prefixes to inject fake control messages. Use dedicated file descriptors, separate pipes, or a side-channel protocol for all sidecar-internal communication.
  6. Resource consumption must be bounded. Every guest-allocatable resource must have a configurable limit enforced by the kernel: filesystem total size, inode count, process count, open FDs, pipes, PTYs, sockets, connections. Unbounded allocation from guest input is a DoS vector. The kernel's ResourceLimits must cover all resource types, not just processes and FDs. Sidecar metadata parsing should start from ResourceLimits::default() and only override keys that are actually present; rebuilding the struct from sparse metadata drops default filesystem byte/inode caps. Per-operation memory guards also live in ResourceLimits: bound pread, fd_write/fd_pwrite, merged spawn argv/env, and readdir batches in crates/kernel/src/kernel.rs, and keep the matching resource.max_* metadata keys in crates/sidecar/src/service.rs in sync so the limits remain configurable. WASM runtime caps are also carried through ResourceLimits: crates/sidecar/src/service.rs maps the configured max_wasm_* fields into reserved AGENT_OS_WASM_* env keys, and crates/execution/src/wasm.rs is responsible for enforcing the resulting fuel/memory/stack limits before guest code runs. WebAssembly parser hardening in crates/execution/src/wasm.rs must stat module files before fs::read(), cap import/memory section entry counts before iterating them, and bound varuint encodings by byte length so malformed or oversized modules fail closed without parser DoS.
  7. Permission checks must use resolved paths. Whenever the kernel checks permissions on a path, it must resolve symlinks first and check the resolved path. Checking the caller-supplied path and then operating on a symlink-resolved target is a TOCTOU bypass. Similarly, link() must check permissions on both source and destination. Rule-based static sidecar policies must use explicit ["*"] wildcards for operations and paths/patterns; empty arrays are invalid policy input and should be rejected instead of treated as implicit wildcards.
  8. The VM must behave like a standard Linux environment. Agents are written to target Linux. The kernel should implement POSIX semantics faithfully -- correct errno values, proper signal delivery, standard /proc layout, expected filesystem behavior. Deviations from standard Linux behavior cause agent failures and must be documented in the friction log (~/.agents/notes/vm-friction.md). When in doubt, match Linux kernel behavior, not a simplified model.

Key Subsystems

  • Virtual filesystem (VFS) -- Layered chunked architecture: ChunkedVFS composes FsMetadataStore (directory tree, inodes, chunk mapping) + FsBlockStore (key-value blob store) into a VirtualFileSystem. Tiered storage keeps small files inline in metadata; larger files are split into chunks in the block store. The device layer (/dev/null, /dev/urandom, /dev/pts/*, etc.), proc layer (/proc/[pid]/*), and permission wrapper sit on top. All layers implement the VirtualFileSystem interface with full POSIX semantics.
  • Process management -- Kernel-wide process table tracks PIDs across all runtimes. Full POSIX process model: parent/child relationships, process groups, sessions, signals (SIGCHLD, SIGTERM, SIGWINCH), zombie cleanup, and waitpid. Each process gets its own FD table (0-255) with refcounted file descriptions supporting dup/dup2. Advisory flock state should stay kernel-global but be owned by the shared open-file-description (FileDescription.id()), keyed by the opened file identity, and released only when the last refcounted FD closes; dup/fork inheritance must see the same lock while separate opens still conflict. Per-FD status bits such as O_NONBLOCK belong on FdEntry / ProcessFdTable, while shared FileDescription.flags() should stay limited to open-file-description semantics such as access mode and O_APPEND; /dev/fd/N duplication can layer new per-FD flags without mutating the shared description. Host-side liveness probes that must not reap runtime children should use waitid(..., WNOWAIT | WNOHANG | WEXITED | WSTOPPED | WCONTINUED) rather than waitpid; the sidecar uses that non-reaping check before signaling host child PIDs to avoid PID-reuse races. Parent-aware waitpid state tracking belongs in crates/kernel/src/process_table.rs: queue stop/continue notifications there, and only let crates/kernel/src/kernel.rs clean up process resources after an exited child is actually reaped. Process exit handling in crates/kernel/src/process_table.rs has to keep child reparenting, orphaned stopped-process-group SIGHUP/SIGCONT delivery, and zombie-aware max_processes accounting aligned; changing only one of those paths breaks Linux-style lifecycle semantics. POSIX signal side effects that depend on the calling PID should stay at KernelVm syscall entrypoints instead of low-level primitives: PipeManager only reports broken-pipe EPIPE, while crates/kernel/src/kernel.rs fd_write is responsible for turning that into guest-visible SIGPIPE delivery. Job-control signal state transitions should stay aligned across crates/kernel/src/process_table.rs and crates/kernel/src/kernel.rs: ProcessTable::kill(...) owns SIGSTOP/SIGTSTP/SIGCONT status changes and waitpid notifications, while PTY resize should emit SIGWINCH from the KernelVm entrypoint after the PTY layer reports the foreground process group.
  • Pipes & PTYs -- Kernel-managed pipes (64KB buffers) enable cross-runtime IPC. PTY master/slave pairs with line discipline support interactive shells. openShell() allocates a PTY and spawns sh/bash.
  • Networking -- Socket table manages TCP/UDP/Unix domain sockets. Loopback connections stay entirely in-kernel. External connections delegate to a HostNetworkAdapter (implemented via node:net/node:dgram on the host). DNS resolution also goes through the adapter.
  • Permissions -- Deny-by-default access control. Four permission domains: fs, network, childProcess, env. Each is a function that returns {allow, reason}. The allowAll preset grants everything (used in agentOS). See "Node.js Builtin Permission Model" in crates/execution/CLAUDE.md for how these interact with the Node.js builtin interception layer.
  • Kernel VM configs must opt into broad access explicitly. KernelVmConfig::new() should stay deny-all by default; tests, browser scaffolds, or other callers that need unrestricted behavior must set config.permissions = Permissions::allow_all() themselves.
  • Browser sidecar workers must mirror execution state into VmState.kernel before or alongside bridge calls. In crates/sidecar-browser/src/service.rs, create browser-worker executions as kernel virtual processes, wire stdin/stdout/stderr through kernel pipes, and reflect kill/exit/output events back into that kernel state instead of treating the browser sidecar as a bridge-only facade.
  • Sensitive mount policy is a separate filesystem capability. Kernel mount APIs check normal fs.write permission on the mount path, and mounts targeting /, /etc, or /proc also require fs.mount_sensitive. In the Rust sidecar, internal create_vm / configure_vm bootstrap work must temporarily clear guest static permissions and restore the configured policy afterward; otherwise default-deny guest policies block the sidecar's own mount and filesystem reconciliation instead of only guest-visible operations.
  • Guest-visible mount denial coverage should exercise the restored kernel path, not just configure_vm(...). In crates/sidecar/tests/service.rs, use ConfigureVm to seed any operator-applied mounts, then call vm.kernel.mount_filesystem(...) under the restored VM policy and assert the mount table is unchanged on EACCES; that keeps bootstrap-mount coverage and guest mount denial coverage separate.
  • Bridge-backed filesystems must preserve missing-path errno semantics. js_bridge/host-backed VFS adapters in crates/sidecar/src/bridge.rs need to map missing stat/lstat lookups to ENOENT instead of a generic bridge EIO; kernel resource-accounting and create-on-write flows probe missing paths before writes, and treating them as I/O failures breaks new-file creation on mounted host filesystems.

Architecture Rules

  • All guest code must execute within the kernel's isolation boundary (WASM or in-kernel isolate). No runtime may escape to a host-native process. If a language runtime requires a JavaScript host (e.g., Emscripten-compiled WASM like Pyodide), the JS host must itself run inside the kernel -- not as a host-side Node.js subprocess. Spawning an unsandboxed host process to run guest code is never acceptable, even as a convenience shortcut.
  • Guest code must never touch real host APIs. Every require('fs'), require('net'), require('child_process'), etc. must return a kernel-backed polyfill. Path-translating wrappers over real node:fs or real node:child_process are NOT acceptable. If a polyfill does not exist yet for a builtin, that builtin must be denied at the loader level until one is built.
  • Native sidecar permission policy has to be available during create_vm, not just configure_vm. Guest env filtering and kernel bootstrap driver registration happen while the VM is being constructed, so AgentOsOptions.permissions must be serialized into the CreateVmRequest.
  • ConfigureVm.permissions is replace-on-write. Omit the field to preserve the VM policy set during create_vm; send an explicit declarative policy object only when the caller intends to replace the current static permission policy.
  • WASM permission tiers must gate host Node WASI access as well as guest-side preopens. In crates/execution/src/wasm.rs, keep Isolated executions off --allow-wasi entirely, and let ReadOnly / ReadWrite / Full differentiate the read/write scope through the guest WASI layer rather than a blanket host flag.
  • sandbox_agent mounts on sandbox-agent@0.4.2 only get basic file endpoints (entries, file, mkdir, move, stat) from the HTTP fs API. When the sidecar needs symlink/readlink/realpath/link/chmod/chown/utimes semantics, it must use the remote process API as a fallback and return ENOSYS when that helper path is unavailable.
  • sandbox_agent pread should prefer HTTP Range on /v1/fs/file, but older remotes may ignore it and return 200 OK. Keep the compatibility path that warns and slices the full response body instead of failing, because the sidecar still has to interoperate with pre-range sandbox-agent servers.
  • Native sidecar security/audit telemetry should use structured bridge events, not ad hoc strings. In crates/sidecar/src/service.rs, emit security-relevant records with bridge.emit_structured_event(...) and include a timestamp field plus stable keys such as policy, path, source_pid, target_pid, or reason so tests and downstream aggregation can assert on them directly.
  • Native mount plugins live under crates/sidecar/src/plugins/ and register through plugins/mod.rs. Keep host_dir, s3, google_drive, and sandbox_agent there with shared registration glue, while bridge.rs only layers on sidecar-specific in-memory and JS-bridge plugin registration.
  • The sidecar execution driver's shell-visible builtin commands must mirror the compat runtime surface. Seed node and wasm during create_vm() and preserve them when refreshing /__agentos/commands/* in configure_vm(), but do not materialize extra /bin/* runtime stubs such as python unless a lower snapshot or installed software explicitly provides them. Direct ExecuteRequest { runtime: ... } dispatch still covers runtimes that are not meant to appear in the guest filesystem.
  • Mounted WASM command directories need both command-map refresh and guest PATH refresh. When crates/sidecar/src/vm.rs rediscovers /__agentos/commands/*, update vm.command_guest_paths and prepend those parent directories into vm.guest_env["PATH"]; crates/sidecar/src/execution.rs child-process resolution should search PATH entries before falling back to /bin, /usr/bin, and /usr/local/bin, or guest child_process.spawn("sh")/spawn("grep") misses mounted registry commands.
  • Native-sidecar command resolution must use the caller's effective PATH, not just vm.guest_env. In crates/sidecar/src/execution.rs, path-like guest candidates emitted by shells (for example /home/agentos/.pi/agent/bin/printf) should only be treated as executables when the candidate or mapped host file actually exists; if the parent directory came from PATH but the file is missing, fall back to the basename so registry command remapping can still find printf/bash/which.
  • Native-sidecar WASM commands see the shadow root, so standard guest directories must be seeded there during VM creation. In crates/sidecar/src/vm.rs, keep /tmp, /var/tmp, /bin, /usr, and the rest of the POSIX bootstrap tree materialized in the shadow root before any WASM command runs, or shell redirection and absolute-path checks will disagree with the kernel VFS (vm.stat("/tmp") works while sh -c 'echo hi > /tmp/x' fails).
  • Host filesystem API writes that should be visible to WASM commands must mirror into the shadow root immediately. In crates/sidecar/src/filesystem.rs, GuestFilesystemOperation::WriteFile needs to update both the kernel VFS and the VM shadow tree right away, or vm.writeFile("/tmp/x") will succeed while guest sh/cat/ls still miss the file until some later sync path runs.
  • Host filesystem API directory creation must mirror into the shadow root too. In crates/sidecar/src/filesystem.rs, GuestFilesystemOperation::CreateDir / Mkdir need to create the same directory under the VM shadow tree immediately, or host-backed shells and JavaScript child-process spawns will lose their requested guest cwd even though vm.stat() sees the directory in the kernel VFS.
  • Host filesystem API rename/remove operations must keep the shadow root in sync too. In crates/sidecar/src/filesystem.rs, GuestFilesystemOperation::Rename, RemoveFile, and RemoveDir need to move or delete the matching shadow-root path immediately, or the next exists/stat shadow reconciliation can resurrect stale host-side paths back into the kernel.
  • Guest process writes that land in the sidecar shadow root must be mirrored back into the kernel before process exit completes. In crates/sidecar/src/execution.rs, sync regular files and symlinks from the shadow-root host tree back into the kernel on ActiveExecutionEvent::Exited, or commands like vm.exec("echo hi > /tmp/x && cat /tmp/x") will succeed while a later vm.readFile("/tmp/x") still returns ENOENT.
  • VM bootstrap must materialize custom root snapshot files into the shadow root, not just standard directories. In crates/sidecar/src/vm.rs, copy non-bundled snapshot/bootstrap entries into the shadow tree during VM creation, or shell-launched WASM commands like zip /archive.zip /hello.txt will create new output files successfully while still missing pre-seeded guest inputs that only exist in the kernel snapshot layer.
  • Top-level sidecar executions must forward the resolved guest cwd into the kernel process too. In crates/sidecar/src/execution.rs, the first kernel.spawn_process(...) for execute requests cannot hardcode /, or vm.exec(..., { cwd }) and relative shell/tool paths drift back to the VM root even when the resolved host cwd is correct.
  • Bidirectional native sidecar wire frames use signed request IDs. request/response frames initiated from TypeScript must keep positive request_id values, while sidecar-initiated sidecar_request/sidecar_response frames must use negative IDs; keep Rust validation, stdio routing, and TS client framing in sync when adding new callback payloads.
  • Sidecar callback request IDs must start negative even in default state. SharedSidecarRequestClient should initialize its counter at -1; a default 0 request ID causes the first sidecar_response to fail protocol validation and leaves callback-driven mounts hanging.
  • Current-thread stdio framing cannot rely on Tokio reader/writer tasks when callback handlers block on sidecar_request responses. In crates/native-sidecar/src/stdio.rs, keep framed stdin/stdout I/O on dedicated OS threads so JS-bridge/binding callback traffic can continue while the main sidecar loop waits synchronously for the host response.
  • Sidecar wire-protocol migrations should preserve the native 4-byte big-endian frame prefix and treat serde_json::Value fields as explicit JSON blobs first. Keep framing changes separate from payload-codec changes, and when defining the BARE schema use a temporary JsonUtf8 boundary for dynamic fields (binding schemas/results, mount configs, bridge args) until both Rust and TypeScript can replace them with typed BARE payloads together.
  • The sidecar BARE wire uses the generated positional tag layout. Do not preserve old hand-assigned union ordinals for byte-for-byte compatibility; client and sidecar are same-version, so Rust/TS codecs should stay self-consistent with protocol/agentos_sidecar_v1.bare and the generated schema.
  • Out-of-band process completions still have to flow through the queued process-event pump. If a sidecar feature emits ProcessEventEnvelopes directly from a background thread instead of via ActiveExecution::poll_event, crates/sidecar/src/execution.rs pump_process_events() must drain process_event_receiver into pending_process_events; otherwise stdio/native clients never observe process_output or process_exited and host waits hang indefinitely.
  • Nested JavaScript child_process polling must flush queued child execution events before finalizing Exited. In crates/sidecar/src/execution.rs, poll_javascript_child_process() should surface any pending child stdout/stderr/sync-RPC events ahead of the terminal exit event; removing the child as soon as Exited arrives drops fast child output and breaks close-driven spawn conformance.
  • Descendant JavaScript child_process sync RPCs must stay recursive. When a nested guest process emits child_process.spawn / poll / write_stdin / close_stdin / kill, service those RPCs against that process's own ActiveProcess.child_processes path instead of falling through to service_javascript_sync_rpc(...); otherwise deeper shell or Node launches route into the filesystem RPC fallback and fail with misleading unsupported JavaScript sync RPC method child_process.* errors.
  • Sidecar env-hardening tests should assert kernel-owned defaults replace host overrides, not that the keys disappear entirely. In crates/sidecar/tests/security_hardening.rs and related guest-identity coverage, expect guest-visible PATH/HOME/PWD to come from the kernel-owned runtime env while internal control vars like AGENT_OS_* and NODE_SYNC_RPC_* stay hidden from process.env.
  • Guest shell invocations of agentos / agentos-* must route through the host-function virtual-process path, not the WASM command path. In crates/native-sidecar/src/execution.rs, child-process command resolution should only tag host-function commands during the read-only resolve pass, then call resolve_host_function_command(...) once a mutable VmState is available during spawn so CLI parsing can still read VM files like --json-file. Treating /bin/agentos* as ordinary WASM entrypoints breaks shell exec handoff because those commands are sidecar-dispatched virtual processes, not guest modules.
  • JavaScript sync RPC option parsing must accept the V8 bridge's raw boolean mkdir shorthand. In crates/native-sidecar/src/execution.rs, javascript_sync_rpc_option_bool(..., "recursive") needs to handle both { recursive: true } and a bare true argument because guest fs.mkdirSync(path, true) can reach the sidecar as [path, true]; treating that as false breaks recursive host-function writes with spurious EEXIST.
  • Loopback TCP tests must use VM-owned listeners unless the port is explicitly exempted. The sidecar blocks outbound guest connections to host-owned 127.0.0.1 ports by default, so network tests in crates/sidecar/tests/ should listen inside the VM and connect to the guest port instead of opening an unrelated host loopback socket.
  • Deleting the legacy guest net.http_request shortcut must not remove the host-to-guest HTTP server loopback path. In crates/sidecar/src/execution.rs, guest http.request() / https.request() should stay on the guest net.connect / tls.connect transport, but vm.fetch() and other host-originated dispatch into guest http.createServer() listeners still rely on serialize_http_loopback_request(...), pending_http_requests, and the "http_request" stream event.
  • Guest 0.0.0.0 / :: listen and bind requests are VM-local aliases, not host wildcard listeners. In crates/sidecar/src/execution.rs, unspecified guest TCP/UDP binds normalize onto loopback-owned sockets while preserving the guest-visible unspecified address for listener/socket snapshots, so tests should assert VM-local reachability and query behavior instead of expecting host-visible wildcard listeners or outright rejection.
  • HTTP/2 loopback listeners need the guest-port mapping and Tokio handoff split correctly. In crates/sidecar/src/execution.rs, resolve HTTP/2 client loopback connects against the per-process HTTP/2 server table before falling back to generic TCP resolution, and when accepting HTTP/2 server sockets convert the blocking std::net::TcpStream into tokio::net::TcpStream inside the per-session Tokio runtime thread, not in the outer accept loop.
  • Secure HTTP/2 bridge flows must thread TLS options through the HTTP/2 RPC payloads and keep ALPN pinned to h2 on both sides. In crates/sidecar/src/execution.rs, net.http2_server_listen and net.http2_session_connect should carry tls payloads into the session launchers, and the TLS defaults need alpn_protocols = ["h2"] or the subsequent h2 handshake can silently fail or downgrade.
  • TLS-capable TCP sockets must not start a background plaintext reader before the first explicit net.socket_read. Eager reads on a cloned TcpStream consume ClientHello/handshake bytes, which breaks net.socket_get_tls_client_hello SNI inspection and net.socket_upgrade_tls on accepted or just-connected sockets.
  • The sidecar TLS reader and writer share one rustls::StreamOwned mutex. In crates/sidecar/src/execution.rs, timed-out TLS read loops must yield briefly after WouldBlock/TimedOut so guest request writes can acquire the lock promptly; otherwise undici/fetch HTTPS flows can hit EOF before the request bytes leave the VM.
  • When splitting crates/sidecar/src/service.rs into domain modules, keep service.rs as the dispatch hub and move domain handlers into sibling-module free functions or impl blocks. Cross-cutting helpers such as JS sync RPC argument parsing and runtime response shims should stay pub(crate) in service.rs, while domain modules (for example filesystem.rs) own the handler logic and are invoked through thin delegating stubs so existing tests and dispatch call sites stay stable during mechanical extractions.
  • Rust permission globs are segment-scoped by default. In kernel/sidecar permission rules, single * and ? match within one path segment and stop at /; use ** when a rule needs to authorize nested paths or resources across separators.
  • Inspection RPC permissions are separate from runtime setup permissions. FindListener / FindBoundUdp require network.inspect, and GetProcessSnapshot requires process.inspect; test fixtures that exercise those handlers still need child_process.spawn and network.listen allowed so the guest process and listener can start before the inspection request runs.
  • Host-function registration bootstrap and invocation use different permission paths. In crates/native-sidecar/src/host_functions.rs, sidecar-owned register_host_callbacks(...) work should temporarily swap the bridge policy to PermissionsPolicy::allow_all() while it refreshes /bin/agentos* command stubs, then restore the VM policy; actual guest host-function execution is separately gated by hostFunction.invoke against <collection>:<function> resources.
  • Sidecar request handlers that need rollback must keep fallible mutations inside a Result-returning closure. In handlers like register_host_callbacks(...), a bare block with ? returns from the whole request handler before rollback runs; wrap the mutation block in (|| -> Result<_, SidecarError> { ... })() so restore/fail-closed cleanup still executes on errors.
  • Native-sidecar VM bootstrap must keep a temporary static policy installed. During create_vm and configure_vm, swap in PermissionsPolicy::allow_all() for sidecar-owned /bin/command-path reconciliation instead of clearing the stored policy entirely, or the LocalBridge fallback will deny internal filesystem checks before the guest-visible policy is restored.
  • Filesystem-side teardown races must fail closed too. In crates/sidecar/src/filesystem.rs, guest filesystem and Python VFS handlers should treat missing VMs or active processes as stale teardown races: log and return a rejection/Ok(()) instead of expect(...), because dispose can win after a request is queued but before the response is emitted.
  • Mapped host paths in crates/sidecar/src/filesystem.rs must resolve symlinks against the mapping root before touching the real host. For Python/Pyodide cache and other AGENT_OS_GUEST_PATH_MAPPINGS accesses, walk existing path components with symlink_metadata, reject targets that leave the mapped root, and filter readdir results the same way so guest stat/listdir calls cannot leak host metadata through escaped symlinks.
  • Mapped host-path metadata and symlink ops must not require a pre-existing kernel mirror. In crates/sidecar/src/filesystem.rs, guest writes under AGENT_OS_GUEST_PATH_MAPPINGS land on the host first, so chmod, utimes, symlink, and readlink need to operate on the mapped host path even when the kernel shadow entry has not been synced yet; only mirror metadata back into the kernel when the guest path already exists there.
  • host_dir metadata ops must resolve beneath the mount root, reject symlink leaves, and NOT require read on the target (non-root sidecar). In crates/native-sidecar/src/plugins/host_dir.rs: chown/utimes resolve the parent via split_parent, reject_symlink_leaf (an fstatat lstat through the anchored parent fd → EPERM on a symlink), then mutate with fchownat/utimensat(.., AT_SYMLINK_NOFOLLOW) against (parent_fd, leaf_name) — no leaf open, so no read requirement, and AT_SYMLINK_NOFOLLOW closes the check→mutate swap race. stat/exists use confine::resolve_parent_beneath + fstatat. chmod is the deliberate exception: fchmodat has no AT_SYMLINK_NOFOLLOW, so it keeps open_metadata_beneath (leaf O_RDONLY | O_NOFOLLOW) + fchmod for TOCTOU safety, accepting the read requirement. Do NOT reintroduce openat2, an O_PATH metadata/leaf anchor, /proc/self/fd re-opens, or a macOS-specific path — the confinement boundary is the single universal confine module (see it and crates/native-sidecar/CLAUDE.md for why openat2 was removed and why the non-root read constraint drives these choices). An O_PATH directory traversal anchor for search-only intermediate dirs (confine::open_dir_anchor, Linux-only, EACCES fallback) is allowed and is not a metadata anchor.
  • VirtualStat schema changes must cross every filesystem boundary together. When adding stat metadata for mount plugins such as host_dir, update the Rust VirtualStat constructors (kernel, device_layer, plugin adapters), sidecar filesystem JSON serialization, JS bridge conversions, and the V8 Stats shim in the same change or the extra metadata gets truncated before guest code can observe it.
  • Stateful JS crypto sync RPCs in crates/sidecar/src/execution.rs are per-process state. Keep service_javascript_crypto_sync_rpc threaded with &mut ActiveProcess for cipher/Diffie-Hellman session handlers, and for AES-GCM treat authTagLength as auth-tag output sizing instead of calling Crypter::set_tag_len() during encryption on the current OpenSSL provider.
  • net.poll waits in crates/sidecar/src/execution.rs must stay explicitly bounded. service_javascript_net_sync_rpc(...) runs on the sidecar's sync-RPC main thread, so clamp guest wait_ms values to the 50 ms ceiling in clamp_javascript_net_poll_wait(...); longer waits should return the currently observed socket state after the ceiling expires instead of monopolizing dispose/shutdown or unrelated VM work.
  • Control-plane stop/continue for shared V8 runtime processes must update kernel state as well as the host runtime PID. In crates/sidecar/src/execution.rs, when kill_process_internal(...) handles SIGSTOP/SIGCONT for uses_shared_v8_runtime() executions, route the signal through vm.kernel.kill_process(...) after signaling the shared runtime so GetProcessSnapshot and wait semantics reflect the requested stopped/running transition.
  • Shared-runtime Wasm executions do not have a per-guest host PID to reap. In crates/sidecar/src/execution.rs, kill_process_internal(...) should terminate embedded Wasm sessions directly for SIGTERM/SIGKILL-style control-plane kills and queue a synthetic ActiveExecutionEvent::Exited(...) when the event channel closes without a process-specific host exit notification; waiting on the shared runtime PID will hang because the embedded runtime process stays alive for other sessions.
  • Nested child_process shell handling must preserve shell builtins without forcing every command through sh -c. In crates/sidecar/src/execution.rs, shell: true requests still need a real shell for builtins like exit, but plain commands such as cat /tmp/file should stay on the direct resolved-command path or sandbox/host path behavior can diverge again.
  • Shell grammar (redirects, pipelines, globbing, quoting) belongs to the VM shell. The kernel exposes process, fd, and VFS primitives; the bridge routes shell-mode commands to /bin/sh -c and never parses shell syntax itself.
  • Resolve symlinked JavaScript entrypoints before launching the guest Node runtime. In crates/sidecar/src/execution.rs, follow node_modules/.bin/* symlinks (and existing shell shims) before treating a file as a JavaScript main module, or relative imports like require("../src/defaults") resolve against the .bin alias instead of the real package bin.
  • The npm/npx display shim should suppress libnpmexec's synthetic > npx banner. In crates/sidecar/src/execution.rs build_host_node_cli_eval(...), the display stub forwards proc-log output directly, so filter the run-script banner emitted for the fake npx lifecycle event or npx -y <pkg> no longer matches native stdout.
  • Process-event handlers must fail closed on stale VM/process state. In crates/sidecar/src/execution.rs and crates/sidecar/src/service.rs, any VM/process lookup reached from queued execution events or JS sync-RPC dispatch should log through log_stale_process_event(...) and return cleanly instead of using expect(...), because teardown can win after an event is queued but before it is drained.
  • Queued stale-process race regressions should use the process-event channel, not direct handler calls. In crates/sidecar/tests/service.rs, keep the sequential smoke path that queues envelopes then disposes, and drive real dispose-vs-send races by cloning process_event_sender, synchronizing sender/dispose with a Barrier, then draining with poll_event_blocking(..., Duration::ZERO) and asserting the bridge captured a log_stale_process_event(...) message.
  • Shared sidecar test helpers should opt into PermissionsPolicy::allow_all() unless a test is explicitly about permissions. In crates/sidecar/tests/support/mod.rs and similar harnesses, permissions: None now means default-deny, so generic runtime/filesystem fixtures need explicit allow-all creation to avoid unrelated regressions under scoped Cargo filters like filesystem.
  • When extracting large sidecar test modules out of src/, keep the tests in crates/sidecar/tests/*.rs by wrapping include!("../src/...") in a same-named module and nesting the moved assertions under mod tests. This preserves the original use super::* access to private helpers, and service-style harnesses also need crate-root re-exports for items that sibling modules import via crate::{DispatchResult, NativeSidecar, SidecarError}.
  • If a shared src/ module carries helper code that only those included test harnesses use, gate the helper types/functions and their imports with #[cfg(test)]. That keeps focused library builds like cargo test -p agentos-sidecar --test protocol warning-free without moving the test scaffold back into production code paths.
  • Sidecar integration tests surface handler failures as Rejected(...) responses, not transport-level Errs. When dispatch_blocking(...) reaches a real request handler and that handler returns SidecarError, assert on ResponsePayload::Rejected { code, message }; reserve expect_err(...) for transport or framing failures that prevent a response frame from being produced.
  • Operator-tunable VM limits live on VmLimits (crates/sidecar/src/limits.rs), parsed from CreateVmRequest.metadata limits.<group>.<field> keys; kernel ResourceLimits keeps its resource.* keys. Every new MAX_*/*_LIMIT/capacity/retention/sizing constant must be classified in crates/sidecar/tests/fixtures/limits-inventory.json (policy wired through VmLimits, or invariant/policy-deferred with a rationale); cargo test -p agentos-sidecar --test limits_audit enforces it.

Testing

  • Rust tests live under tests/, not inline #[cfg(test)] mod tests in src/. Move every inline test module in Rust crates to the crate's tests/ directory. Exceptions must be justified (e.g., testing a private internal that can't be reached from an integration test).
  • Some V8-backed Rust integration binaries still trip teardown/init crashes or SIGSEGVs when multiple libtest cases share the same binary. For those targets, keep coverage in one top-level suite test per crates/*/tests/*.rs file until the shared-runtime teardown bug is fixed.
  • Node builtin compatibility regressions should usually land in crates/sidecar/tests/builtin_conformance.rs: that harness runs the same probe script under host Node and guest V8 and asserts exact JSON parity, which is the fastest way to catch module-shape mismatches like require("events").
  • Keep crates/sidecar/tests/builtin_conformance.rs as one top-level builtin_conformance_cases test that forks per-case subprocesses via AGENT_OS_BUILTIN_CONFORMANCE_CASE; that preserves the exact cargo test -p agentos-sidecar --test builtin_conformance acceptance surface while isolating V8-backed cases from each other.
  • For sidecar POSIX conformance coverage, keep /proc, /dev, waitpid, and process-group assertions as direct agentos_kernel / ProcessTable tests where possible, and reserve the sidecar/V8 harness for guest-observable behaviors such as signal delivery that require the full runtime bridge.
  • Run scoped tests: cargo test -p agentos-sidecar -- test_name_filter or cargo test -p agentos-sidecar --lib (lib unit tests only, skip integration tests).
  • Never run bare cargo test -p agentos-sidecar without a filter -- integration tests spawn real processes and can hang.
  • In crates/sidecar/tests/service.rs, keep the broad V8-backed integration coverage under a single top-level test because multiple libtest cases in that binary still trip V8 teardown crashes. When a PRD acceptance command needs a filter token like javascript_network_dns, preserve that token in the single suite test name instead of adding another top-level wrapper.
  • In crates/sidecar/tests/service.rs, permission tests that only need a parent JavaScript process for child-process bookkeeping should seed an ActiveProcess with kernel.create_virtual_process(...) instead of start_fake_javascript_process(...); this exercises the sidecar path without coupling the scoped suite to V8 teardown state.
  • Split execution/tests/javascript.rs (46 tests) into javascript/{builtin_interception,module_resolution,env_hardening,sync_rpc}.rs.
  • Do not hide first-party Rust coverage behind #[ignore]; if an integration test is too expensive or flaky for the default suite, split or scope it so the active acceptance command can still run it honestly.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.