{"owner":"superradcompany","repo":"microsandbox","hasSkills":true,"totalSkillsCount":12,"totalTokensCount":17693,"categories":["anthropic-skill","root-instruction","plugin-manifest"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/microsandbox-docs/SKILL.md","AGENTS.md","packages/agent-client/README.md","packages/agent-client/rust/README.md","packages/agent-client/typescript/README.md","packages/control-client/rust/README.md","packages/control-client/typescript/README.md","packages/microsandbox-types/README.md","packages/microsandbox-types/rust/README.md","packages/microsandbox-types/typescript/README.md","packages/protocol-client/rust/README.md","packages/protocol-client/typescript/README.md"],"skills":{".agents/skills/microsandbox-docs/SKILL.md":"---\nname: microsandbox-docs\ndescription: Edit Microsandbox product, CLI, and SDK documentation with verified behavior, concise examples, and the repository's Mintlify conventions. Use for changes under microsandbox/docs.\n---\n\n# Microsandbox docs\n\nFollow the repository's `AGENTS.md` for contribution rules and SDK reference structure. Read the affected pages and check the working tree before editing. If a target branch is specified, start from its latest commit and preserve unrelated branch changes.\n\n## Verify the feature\n\n- Check implementation, configuration schema, CLI flags, and supported SDKs before naming an option or writing an example. Distinguish per-secret settings from sandbox-wide defaults and secret-policy denials from ordinary network denials.\n- Check the published tag or release before attaching a version pill. A merged change is not evidence that a tagged version contains it. State local-only or cloud limitations only where they affect the documented workflow.\n- Make examples internally complete. When an example expects a JSON HTTP denial response, show the response enablement, JSON format, and applicable message setting together, even if some are current defaults.\n\n## Write the page\n\n- Lead with what the reader can do and the shortest useful example. Put the main code sample on the page; use accordions for limits and secondary details.\n- Use short headings that name the operation. Keep violation actions, host permissions such as placeholder passthrough, and response-message configuration conceptually separate.\n- Use `CodeGroup` for supported language examples when a workflow spans SDKs. Show a CLI example only when an equivalent flag or command exists. Put `sandbox.yml` in a YAML configuration section instead of presenting it as a CLI command.\n- Keep prose independent of language-specific syntax when possible. Put language or CLI details beside their example or in the relevant reference page. Preserve meaningful edge cases without repeating them across pages.\n\n## Validate and deliver\n\n- Run `python3 scripts/check-docs-language-order.py` and `git diff --check`. Parse changed YAML examples, check links and anchors, and load the affected pages in the Mintlify preview when available.\n- Report the changed pages, checks, and any behavior or release ambiguity. Follow the user's requested target branch and publishing route.\n","AGENTS.md":"# AGENTS.md\n\nThese instructions are only for agents contributing changes to this repository.\nDo not apply them to other repositories or to general agent behavior outside this contribution workflow.\n\n## Scope\n\n- Follow these guidelines when editing code, docs, tests, examples, CI, or release files for microsandbox.\n- Prefer repository conventions over generic agent habits. When unsure, inspect nearby files and match their style.\n- Do not create branches, commit, push, tag, publish, or open pull requests unless the human explicitly asks.\n- Check `git status --short --branch` before making changes. Do not overwrite or revert user work unless explicitly asked.\n\n## Project Map\n\n- `sdk/rust` is the public Rust SDK crate.\n- `crates/cli` contains the `msb` CLI.\n- `crates/runtime` contains VM runtime integration.\n- `crates/filesystem`, `crates/image`, `crates/network`, `crates/db`, `crates/migration`, `crates/metrics`, `crates/metrics-collector`, `crates/protocol`, and `crates/utils` are shared internal crates.\n- `packages/agent-client` and `packages/microsandbox-types` are the shared agent-protocol client and wire-contract type packages, each with Rust and TypeScript implementations.\n- `crates/agentd` is the in-guest agent. It is a workspace member; the musl guest binary that ships in releases is built separately.\n- `sdk/python`, `sdk/node-ts`, and `sdk/go` contain the language SDKs and native bindings.\n- `docs/` contains the documentation site. Keep docs in sync with user-facing behavior.\n- `examples/` contains runnable examples. Add new example projects only when requested or clearly required by the contribution.\n- `mcp/` and `skills/` are submodules related to agent integrations.\n- `vendor/libkrunfw` is a submodule for the kernel firmware library.\n\nRepository layout:\n\n```text\n.\n|-- AGENTS.md\n|-- Cargo.lock\n|-- Cargo.toml\n|-- COMPATIBILITY.md\n|-- DEVELOPMENT.md\n|-- Dockerfile.agentd\n|-- justfile\n|-- msb-entitlements.plist\n|-- assets/\n|-- crates/\n|   |-- agentd/\n|   |-- cli/\n|   |-- db/\n|   |-- filesystem/\n|   |-- image/\n|   |-- metrics/\n|   |-- metrics-collector/\n|   |-- migration/\n|   |-- network/\n|   |-- protocol/\n|   |-- runtime/\n|   |-- testing/\n|   |   |-- init/\n|   |   |-- macros/\n|   |   `-- utils/\n|   `-- utils/\n|-- docs/\n|   |-- changelog/\n|   |-- cli/\n|   |-- getting-started/\n|   |-- images/\n|   |-- networking/\n|   |-- observability/\n|   |-- recipes/\n|   |-- sandboxes/\n|   |-- sdk/\n|   `-- security/\n|-- examples/\n|   |-- python/\n|   |-- rust/\n|   `-- typescript/\n|-- mcp/\n|   |-- bin/\n|   |-- src/\n|   `-- package.json\n|-- packages/\n|   |-- agent-client/\n|   `-- microsandbox-types/\n|-- packaging/\n|   `-- docker/\n|-- scripts/\n|   `-- smoke/\n|-- sdk/\n|   |-- go/\n|   |-- node-ts/\n|   |-- rust/\n|   `-- python/\n|-- skills/\n|   `-- microsandbox/\n`-- vendor/\n    `-- libkrunfw/\n```\n\n## Design Principles\n\n- Before making or continuing a change that may introduce a regression, breaking change, or backward-compatibility risk identified below, stop and alert the human with the likely impact and affected workflows.\n- Keep changes narrowly scoped to the requested behavior. Avoid drive-by refactors, unrelated formatting, or dependency churn.\n- Treat sandbox isolation, host filesystem access, networking, and secret handling as security-sensitive. Validate inputs at boundaries and avoid exposing host paths, credentials, or ambient privileges.\n- For public APIs, keep the Rust SDK, CLI, Python SDK, Node SDK, Go SDK, docs, and examples consistent when they describe the same capability.\n- Prefer explicit errors with useful context over silent fallbacks.\n\n## Host Path Handling\n\n- For every path input, identify whether it belongs to the local host, a remote backend, or the guest before resolving it. Never resolve cloud or guest paths against the SDK client's working directory.\n- Resolve local host paths against their documented base once, before deferred use or persistence. Retained handles and asynchronous operations must reuse that resolved path, including their final writes. Sandbox and SDK configuration-file inputs use the contributing file's directory, including managed SDK settings.\n- Making a path absolute must preserve its symlink policy. Do not substitute filesystem canonicalization or collapse parent components across symlinks without reviewing the behavior change.\n- Preserve existing persisted relative paths and their legacy startup/resource-inheritance behavior. They have no reliable original base unless it was saved: do not reject, migrate, prompt about, or rewrite them during restart. Capture absolute host inputs only for new sandboxes, including new restore/branch children, without modifying their source sandbox.\n- Path changes need regression coverage with different creation and consumption directories, plus missing targets and symlinks where applicable. Change process cwd only inside an isolated test subprocess. Review sandbox mounts, rootfs paths, TLS files, backend storage roots, snapshots, transfers, and generated commands when adding a new path input.\n\n## Backward Compatibility Review\n\nBackward-compatibility detection is a required part of working on this project. Surface potential compatibility breaks before making or continuing the affected change.\n\nThe review's immediate goal is proactive detection and reporting, not automatically implementing compatibility fixes. Do not silently add compatibility layers, migrations, legacy codecs, fallback paths, or downgrade behavior. When a material risk is found, explain the affected releases, components, persisted artifacts, users or workflows, the likely failure mode, and the available options; then wait for human direction as required by the Design Principles above.\n\nSDK-to-`msb` compatibility is required in both directions for future changes:\n\n- Newer SDKs must continue to work with older `msb` binaries for existing supported workflows.\n- Newer `msb` binaries must continue to work with older SDKs for existing supported workflows.\n- Apply this requirement to every language SDK and the complete SDK/runtime interaction, including binary resolution, launch arguments and JSON, inherited descriptors, startup responses, control and agent protocols, and shared persisted state. Compatibility with an already-running agent alone does not establish launch compatibility.\n- A new feature unavailable in an older peer must be detected and produce a clear unsupported-feature or upgrade-required error, or use an explicitly approved fallback. It must not silently lose requested behavior or break unrelated existing functionality.\n- Do not assume matching package versions or bundled binaries satisfy this requirement. Review independently installed runtimes, including those resolved from `MSB_HOME`, and use cross-version tests or historical fixtures for affected boundaries; same-version tests alone are insufficient evidence.\n\nBidirectional SDK/runtime compatibility does not mean freezing the shared catalog at the oldest installed or running version. Newer SDKs and CLIs may apply validated catalog upgrades while older VM runtimes remain running. Do not add a blanket \"stop all sandboxes before upgrading\" gate or preserve an old catalog solely because an older runtime is present. Distinguish an older VM process continuing its database writes from an older SDK/CLI reopening the catalog and running its own schema-admission checks; test and report these separately rather than treating one as evidence for the other.\n\nFor online catalog upgrades, preserve migration serialization, transaction safety, recovery journals, and active maintenance leases. Evaluate the actual SQL and persisted-data contracts used by older runtimes; neither an additive-looking migration nor a version difference alone proves safety or incompatibility. Live tests must exercise an older VM across the upgrade, verify retained execution and data, and cover new-SDK launch, control, restart, and cleanup through older runtimes. If a concrete incompatible migration is found, report that specific conflict for a decision rather than reintroducing a blanket compatibility blocker.\n\nIf a proposed change may violate either direction, flag it before implementation and wait for human direction; this requirement does not authorize silently building adapters or choosing a breaking change. Any exception or change to the supported compatibility horizon requires explicit human direction. The known v0.6.9 ↔ v0.6.10 launch incompatibility is an accepted historical exception and must not be repaired as part of unrelated work. It does not exempt future changes from this review or requirement.\n\nBy explicit user direction on 2026-09-13, the historical SDK/runtime compatibility target for this work is v0.6.x, with v0.6.0 as the floor, against the current implementation candidate in both directions. Releases older than v0.6.0, including all v0.4.x and v0.5.x releases, are excluded from required compatibility. Preserve their historical results as diagnostic evidence. This supersedes the earlier exact-v0.5.0 exclusion. Existing codecs and capability gates are not removed or changed by this scope decision. The known v0.6.9 ↔ v0.6.10 exception remains unchanged; other v0.6.x failures are not waived.\n\nBefore changing an existing cross-version boundary, determine:\n\n- What older component, binary, sandbox, or persisted state may interact with the change.\n- Which side is the old producer or consumer.\n- Whether failure would be a clean refusal, loss of functionality, stranded state, silent misinterpretation, data loss, or possible corruption.\n- Whether the repository has a fixture or test using an actual older artifact or binary, rather than only same-version round trips.\n\nAlways perform this review when a change affects any of these areas:\n\n- Host-to-guest framing, message names, flags, IDs, payload fields, version negotiation, bootstrap, or lifecycle handshakes.\n- Unix sockets, Windows named pipes, path hashing, relay routing, control messages, inherited file descriptors, signals, or process-launch JSON.\n- SQLite schemas, migration history, persisted configuration, downgrade behavior, journals, leases, or runtime recovery state.\n- ext4, EROFS, VMDK, OCI layers, filesystem metadata, device IDs, mount tags, block paths, or other on-disk formats.\n- Snapshots, archives, manifests, canonical serialization, digests, parent identities, filenames, or atomic publication order.\n- Cache keys, materializer ABIs, layer ordering, whiteouts, hardlinks, xattrs, or content-addressed storage.\n- The runtime, agentd, libkrun, firmware, kernel patches, virtio devices, shared-memory layouts, or package-version coupling.\n- Network address derivation, MACs, interface names, DNS aliases, published ports, TCP/UDP behavior, vsock, SSH, or SFTP behavior.\n- Heartbeats, boot errors, metrics, logs, lock files, runtime directories, or other files consumed across process or release boundaries.\n\nCheck each applicable compatibility direction:\n\n1. A new host interacting with an old running sandbox and old agentd.\n2. A new release opening an existing `MSB_HOME` and its database.\n3. A new release reading old disks, snapshots, archives, caches, and filesystem metadata.\n4. An older release encountering state written by the new release, including downgrade refusal behavior.\n5. Exported artifacts moving between releases, platforms, or architectures.\n6. Independently running components from different releases communicating during an upgrade.\n7. A newer SDK launching and operating an older `msb` binary.\n8. An older SDK launching and operating a newer `msb` binary.\n\nTreat stable strings, numeric constants, paths, hashes, serialized field details, ordering guarantees, timing, and error interpretations as compatibility-sensitive even when they are not part of the public API.\n\nFor changes affecting persisted data or cross-version communication, read [COMPATIBILITY.md](COMPATIBILITY.md) before implementation. Follow its contract-version naming, module ownership, migration, and validation guidelines.\n\n## Path Handling\n\n- Every relative path must have an explicit base directory. Never rely on the working directory at the time of eventual use.\n- Resolve CLI paths against the invocation directory, config-file paths against the config file’s directory, and SDK paths against a documented base.\n- Capture that base and resolve host paths before spawning asynchronous work or passing them to another process.\n- Persist absolute host paths when they reference a fixed local resource that must remain the same across restarts.\n- Distinguish host paths, guest paths, volume-relative paths, and resource identifiers. Do not apply host-path normalization to all strings.\n- Making a path absolute, collapsing `..`, and resolving symlinks are different operations. Choose deliberately; they can select different destinations.\n- Use structured path fields internally. Avoid concatenating paths into delimiter-separated strings that become ambiguous with valid filenames.\n- Return explicit errors when resolution fails. Never silently substitute another directory or panic.\n- Enforce filesystem containment during the operation, accounting for symlinks and concurrent changes. String-prefix checks alone are insufficient.\n- Centralize path-resolution rules so CLI, SDK, and background execution cannot drift.\n\nFor path-related changes, test creation in directory A followed by use from directory B, plus relevant symlink, `..`, missing-path, and platform-specific cases. Assert the exact file or directory accessed—not just whether the operation succeeded.\n\n## Rust Layout And Style\n\n- Most Rust crates use `lib/lib.rs` for library code and `bin/main.rs` for binaries. Keep using those paths for new crate entries unless the surrounding crate already does something different.\n- When adding a new library or binary target, declare the path explicitly in `Cargo.toml`:\n\n```toml\n[lib]\npath = \"lib/lib.rs\"\n\n[[bin]]\nname = \"example\"\npath = \"bin/main.rs\"\n```\n\n- Keep crate roots and module roots thin. `lib.rs` and `mod.rs` should declare modules, crate attributes, and exports only. Put implementation in leaf modules such as `sandbox/config.rs`, `policy/types.rs`, or `commands/run.rs`.\n- File order should be:\n  1. Module docs and crate/file attributes, such as `//! ...` and `#![warn(missing_docs)]`.\n  2. `use` imports.\n  3. Sectioned items.\n- Group imports by origin, separated by blank lines: standard library first, external crates second, then `crate::` and `super::` imports.\n- Do not put `use` statements inside sections unless there is a narrow local reason, such as a test module import.\n- Strongly prefer importing types at the top of the file or test module over spelling out full module paths in signatures, patterns, and function bodies. Use an import alias for name collisions; keep qualified paths when they meaningfully clarify an otherwise ambiguous name.\n- Use the exact section delimiter shown below. Do not invent alternate Markdown-style, shorter, or decorative section headers.\n- Include only sections that contain items. Do not add empty sections just to satisfy the full order.\n- Organize Rust files with these section headers, in this order when applicable:\n\n```rust\n//--------------------------------------------------------------------------------------------------\n// Constants\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Types\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Methods\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Trait Implementations\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Functions\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Macros\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Tests\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Re-Exports\n//--------------------------------------------------------------------------------------------------\n```\n\n- Aggregator files that only expose modules and public items may use `Exports` instead of `Re-Exports` when matching existing files.\n- Use qualified section labels only to split large sections into obvious groups, for example `Types: Identifiers`, `Functions: Handlers`, or `Functions: Helpers`.\n- Do not create a qualified section for one or two items unless the surrounding file already uses that pattern.\n- Put constants and statics under `Constants`.\n- Put `struct`, `enum`, `trait`, and `type` definitions under `Types`.\n- Put inherent `impl Type` blocks under `Methods`, directly after the related type definitions when practical.\n- Put `impl Trait for Type` blocks under `Trait Implementations`.\n- Put free functions under `Functions`. If a free function is only used by one public function, place it later in `Functions: Helpers`.\n- Put macros under `Macros`, not near the call site.\n- Put unit tests under `Tests`, usually as `#[cfg(test)] mod tests`. Keep test-only helpers in the same section.\n- Put public re-exports under `Re-Exports`, or `Exports` in root files that use the existing aggregator style.\n- Keep items in dependency order inside a section: public surface first, private helpers later.\n- Keep docs on public types, fields, methods, functions, and modules. This repo uses `#![warn(missing_docs)]` in public crates, so new public items should explain what they are for.\n- Prefer explicit domain types over loosely typed strings, booleans, or tuples when the value crosses an API or subsystem boundary.\n- Separate logical steps with blank lines: setup, early returns, normalization, validation, operations, and final results. Apply the same spacing to test setup, actions, and assertions; do not rely on `cargo fmt` to provide it.\n- Prefer named intermediate values when an expression combines distinct steps, especially fallible lookups, parsing, and validation. Make each failure point easy to identify.\n- Keep iterator chains simple. Prefer a straightforward loop when nested closures or branching make the logic harder to follow; do not add helpers solely to hide that complexity.\n- Keep straightforward logic together. Extract helpers when they clarify a distinct responsibility or remove meaningful duplication, rather than merely shortening a function.\n- Write comments to explain intent, constraints, or non-obvious behavior rather than narrating the code.\n- Name values for what they currently represent. Avoid names that imply an action has already happened when the value describes a requirement or planned action.\n- During refactors, conflict resolution, bug fixes, and feature work, call out any expected behavior, API, or data-format changes and wait for direction when the risk is material.\n- Use `thiserror` or existing local error patterns for typed errors. Include enough context for callers to understand the failing operation.\n- Keep shared library and SDK errors interface-neutral: describe the problem and remedy without CLI flags or command syntax. Add command-specific guidance in the CLI layer.\n- In async code, avoid holding locks across `.await`. Prefer explicit ownership, short critical sections, and existing Tokio patterns in the surrounding module.\n- Keep feature-gated code close to the feature it gates and use existing `#[cfg(feature = \"...\")]` patterns.\n- Do not add examples under `examples/` unless requested or clearly required. Prefer tests and docs for small usage coverage.\n- Run `cargo fmt` before finalizing Rust changes.\n\n## Development Build Notes\n\n- If you build `msb` to run it locally on macOS, make sure the binary is codesigned with `msb-entitlements.plist`; otherwise VM/runtime failures may be caused by missing entitlements instead of your code change.\n- Prefer `just build` or `just build-msb` when producing a runnable local binary. The macOS recipe rebuilds `msb` and runs:\n\n```bash\ncodesign --entitlements msb-entitlements.plist --force -s - build/msb\n```\n\n- If you bypass `just` and call `cargo build` directly, manually codesign the exact `msb` binary you are going to run before testing sandbox startup, protocol, networking, or filesystem behavior.\n\n## Validation\n\nUse focused checks for the files you touched, then broader checks when the change crosses crate, SDK, CLI, or runtime boundaries.\n\nCommon Rust checks:\n\n```bash\ncargo fmt --all -- --check\ncargo clippy --workspace -- -D warnings\ncargo test --workspace\ncargo build -p microsandbox-cli\n```\n\n`agentd` is a workspace member, so the workspace-wide commands above cover it. The musl guest binary that ships in releases is built separately via `just build-agentd`.\n\nPython SDK checks:\n\n```bash\ncd sdk/python\nuv sync --group dev\nuv run maturin develop --release\nuv run pytest\nuv run ruff check .\n```\n\nNode SDK checks:\n\n```bash\ncd sdk/node-ts\nnpm ci\nnpm run build\nnpm test\nnpm run typecheck\n```\n\nGo SDK checks:\n\n```bash\ncd sdk/go\ngo test -count=1 .\ngo test -tags \"smoke microsandbox_ffi_path\" -count=1 -timeout 2m .\n```\n\nIntegration tests may require Linux with KVM or macOS Apple Silicon support. If a needed check cannot run in the current environment, say exactly which command was skipped and why.\n\nThe full local setup and build loop is documented in `DEVELOPMENT.md`. Use `just setup`, `just build`, and `just install` when you need the full local runtime, `agentd`, or `libkrunfw` artifacts.\n\n## Commits\n\n- Use Conventional Commits for commit titles: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `ci`, or `build`.\n- Use a scope when it clarifies the affected area, for example `fix(network): ...` or `docs(sdk): ...`.\n- Keep the subject imperative, lowercase after the colon, no trailing period, and at most 72 characters.\n- Include a commit body for non-trivial changes. Explain what changed and why.\n- Use signed commits: `git commit -S`.\n- Before committing, inspect the actual diff, including new, modified, and deleted files. Do not write a commit message from filenames or previous commit messages alone.\n- If there is nothing to commit, say so rather than creating an empty commit.\n\nExample:\n\n```text\nfix(network): wake smoltcp after accepted host connections\n\nNotify the network loop after accepting a published-port connection so\npending guest traffic can make progress without waiting for another timer.\n```\n\n## Branches And Pull Requests\n\n- You may make local edits while on `main`, but do not commit directly to `main`. Start from the latest `main` when creating a contribution branch.\n- Use short, descriptive, kebab-case branch names. Avoid personal prefixes in shared documentation unless the maintainer asks for one.\n- Before opening a PR, compare against the intended base branch and inspect the actual diff:\n\n```bash\ngit log origin/main..HEAD --oneline\ngit diff origin/main..HEAD --stat\ngit diff origin/main..HEAD --name-status\n```\n\n- PR titles should follow Conventional Commit style and stay under 72 characters.\n- PR descriptions should be plain and accurate:\n  - `## TL;DR`: one or two short sentences.\n  - `## Description`: a flat bullet list of core changes.\n  - `## Test Plan`: concrete commands or observable checks.\n- Do not use emojis in PR titles or descriptions.\n- If a PR description includes an API example, verify every symbol, path, flag, type, field, and signature against the diff before writing it.\n\n## Version And Release Changes\n\n- Do not bump versions, publish packages, create release tags, or modify release automation unless explicitly asked.\n- All released packages share a version. When a version bump is requested, check `Cargo.toml`, `sdk/node-ts/package.json`, `mcp/package.json`, and any other package metadata touched by the release process in `DEVELOPMENT.md`.\n- For release or version PRs, summarize the user-visible changes since the previous version bump and run the relevant dry-run publish checks when practical.\n\n## Documentation And Examples\n\n- For changes under `docs/`, read [the Microsandbox docs skill](.agents/skills/microsandbox-docs/SKILL.md) for page structure, examples, version labels, and validation.\n- Update docs when behavior, configuration, CLI flags, SDK APIs, or examples change.\n- Keep SDK reference introductions to a short purpose statement and a link to the relevant usage guide, then go straight into API entries. Follow the concise structure of the execution and networking reference pages across all SDK languages.\n- Start reference type sections directly with their members when the type heading is sufficient. Avoid redundant “Built by” / “Used by” rows and introductory text that repeats the member descriptions.\n- Do not prepend feature walkthroughs, lifecycle summaries, implementation details, or long examples to reference pages or type sections. Put API-specific details beside the relevant member and broader explanations in the usage guides; avoid duplicating those guides in the reference.\n- In SDK reference pages, wrap usage examples in a collapsed `<Accordion title=\"Example\">`. Keep API signatures and type definitions visible outside the accordion.\n- When removing reference overviews, preserve unique signatures, constraints, result fields, and resource-cleanup requirements beside the relevant member or in the linked usage guide.\n- Omit field lists that only repeat documented builder options. Keep field references for returned data and properties not covered by the builder.\n- Do not use tables in SDK references. Use individual method and property headings with visible signatures and the existing parameter/return blocks. Group returned hash or record fields in the existing field blocks, including types, descriptions, and defaults where needed. Preserve links and existing anchors, and avoid repeating the same description above and inside a return block.\n- In Rust documentation examples, import types at the top of each snippet and use their short names in the body instead of fully qualified paths. Group standard-library imports before external-crate imports.\n- Keep examples realistic and runnable. Do not invent APIs or flags.\n- Prefer editing existing examples over adding new example projects unless the new example is requested or clearly fills a missing user workflow.\n- Documentation should describe current behavior, not future plans, unless the page is explicitly about roadmap work.\n\n## Agent Operating Rules\n\n- Use `rg` or `rg --files` for repository searches.\n- Read the relevant files before editing. Let existing module boundaries guide the change.\n- Make the smallest coherent change that satisfies the request.\n- Avoid destructive git commands such as `git reset --hard` and `git checkout --` unless explicitly requested.\n- Do not edit generated artifacts, lockfiles, or submodule pointers unless the change requires it.\n- If generated files or lockfiles must change, explain why in the final summary.\n- Report what changed, what validation ran, and any checks that were skipped.\n","packages/agent-client/README.md":"# microsandbox agent client\n\nTransport-agnostic clients for speaking the microsandbox agent protocol.\n\nThis layer sits between the protocol package and the high-level SDKs:\n\n```text\nsdk -> agent-client -> protocol-client -> protocol\n```\n\nThe agent package supplies connection handshakes, its existing message codec, and protocol-generation gates. Its public client is a specialization of the shared generic router, which owns IDs, framing, transport lifetime, and request/stream routing. SDK packages own sandbox lifecycle, name resolution, image management, volumes, and metrics.\n\n## Layout\n\n```text\npackages/agent-client/\n├── rust/\n└── typescript/\n```\n\n- `rust/` publishes as `microsandbox-agent-client`.\n- `typescript/` publishes as `@microsandbox/agent-client`.\n\nThe Rust crate has no default transport feature. Local SDK connections opt into `uds` on Unix or `named-pipe` on Windows. An owned `AsyncRead + AsyncWrite` transport can also be supplied, including a caller-authenticated WebSocket adapted to bytes.\n\nThe TypeScript package exposes a browser-safe default entry and keeps Node-only Unix sockets behind `@microsandbox/agent-client/node`; browser/front-end callers use `WebSocketTransport`.\n\n## Message Model\n\nThe public API names protocol boundaries by structure:\n\n- `TypedMessage`: message type plus a native payload object.\n- `EncodedMessage`: message type plus already-CBOR-encoded payload bytes.\n- `RawFrame`: routing fields and an opaque envelope body.\n- `Message` (Rust) / `InboundFrame` (TypeScript): an inspectable envelope retaining its original raw frame.\n- `TransportPacket`: exact transport bytes, including length prefix.\n\nUnchecked packet writes are an explicit escape hatch for relays, tests, and specialized tooling.\n\nRust and TypeScript preserve native, encoded, raw, exact-packet, explicit-ID, and owned split-stream access. See each package README for the API migration and validated examples. Shared TypeScript packages build together through the private npm workspace in `packages`.\n","packages/agent-client/rust/README.md":"# microsandbox-agent-client\n\n`AgentClient` is `Client<AgentProtocol>` from `microsandbox-protocol-client`. The agent package supplies relay setup and generation metadata; the shared engine supplies one reader, writer, allocator, and ownership registry. SDK services such as execution collection and filesystem handles remain in `microsandbox`.\n\n```text\nTypedMessage / EncodedMessage / raw envelope / exact packet\n                           |\n                 Client<AgentProtocol>\n                           |\n          current or legacy relay handshake\n                           |\n                       agent relay\n```\n\n## Native, encoded, and raw messages\n\n```rust,no_run\nuse microsandbox_agent_client::{AgentClient, TypedMessage, EncodedMessage};\nuse microsandbox_protocol::{fs::{FsOp, FsRequest, FsResponse}, message::MessageType};\n\nasync fn inspect(client: &AgentClient) -> Result<(), Box<dyn std::error::Error>> {\n    let request = FsRequest {\n        bulk: None,\n        op: FsOp::Stat { path: \"/etc/os-release\".into(), follow_symlink: true },\n    };\n    let reply = client.request(TypedMessage::new(MessageType::FsRequest, &request)).await?;\n    let response: FsResponse = reply.payload()?;\n    println!(\"{}\", response.ok);\n\n    // The caller's payload bytes are not parsed or normalized by this path.\n    let payload = vec![0xa0];\n    let _reply = client.request(EncodedMessage::new(MessageType::Ping, payload)).await?;\n    Ok(())\n}\n```\n\n`Message.t` is the actual wire string. Unknown names and unknown envelope fields remain inspectable: `Message::raw()` borrows the original frame and `into_raw()` returns it. Ordinary requests surface peer error messages as messages. Optional `Request<AgentProtocol>` implementations add checked unary decoding without narrowing the generic API.\n\n## Owned streams and explicit IDs\n\n```rust,no_run\nuse microsandbox_agent_client::{AgentClient, TypedMessage};\nuse microsandbox_protocol::{exec::{ExecRequest, ExecStdin}, message::MessageType};\n\nasync fn exchange(client: &AgentClient, request: &ExecRequest) -> Result<(), Box<dyn std::error::Error>> {\n    let stream = client.stream(TypedMessage::new(MessageType::ExecRequest, request)).await?;\n    let id = stream.id();\n    let (sender, mut receiver) = stream.into_parts();\n    client.send(id, TypedMessage::new(MessageType::ExecStdin, ExecStdin { data: vec![] })).await?;\n    sender.send(TypedMessage::new(MessageType::ExecStdin, ExecStdin { data: b\"input\".to_vec() })).await?;\n    while let Some(message) = receiver.recv().await? {\n        println!(\"{}\", message.t);\n    }\n    Ok(())\n}\n```\n\nBoth split parts retain the connection and ID lease; sender handles may be cloned and there is one consuming receiver. Terminal receipt disables sends and delivers the terminal frame once. Closing or dropping the receiver disables sends and retains bounded drain state until terminal completion. It sends no process signal or filesystem EOF. Transport loss before a terminal frame is an error, distinct from clean exhaustion.\n\n`request_raw(flags, body)`, `stream_raw(flags, body)`, and `send_raw(id, flags, body)` exchange opaque envelopes without CBOR decoding. `write_unchecked(packet_bytes)` serializes exact caller bytes without allocating an ID or subscribing for replies. `TransportPacket` remains an optional standalone framing helper; `packet.into_bytes()` feeds the exact write API. Standalone packet readers require exclusive transport ownership.\n\n## Setup and metadata\n\nEnable `uds` for native Unix sockets or `named-pipe` for Windows pipes. `connect_stream` accepts an owned `AsyncRead + AsyncWrite + Unpin + Send` transport, including `UdsTransport` and `NamedPipeTransport`. The `stream` feature name remains accepted for existing manifests; generic owned transports are available without additional dependencies.\n\n```rust,no_run\nuse microsandbox_agent_client::AgentClient;\nuse std::time::Duration;\n\nasync fn connect(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {\n    let client = AgentClient::connect_with(path, |o| o.setup_timeout(Duration::from_secs(5))).await?;\n    println!(\"{}\", client.ready().agent_version());\n    println!(\"{}\", client.ready().negotiated_version);\n    println!(\"{} ready bytes\", client.ready().ready_bytes().len());\n    client.close().await;\n    Ok(())\n}\n```\n\nSetup has one deadline across dial and handshake. The current `[id_min,id_max]` relay prologue and the supported pre-0.5 `[id_offset]` prologue retain their existing parsing, ID ranges, and ready payload behavior. Known operations use the smaller host/peer generation as their availability gate. Current connections continue emitting the host's existing envelope generation; legacy connections emit generation one. Source-level API changes do not retire the legacy wire path.\n\n## Migration from the previous active client\n\n| Previous API | Shared-client API |\n| --- | --- |\n| `connect(path)` / `connect_stream(stream)` | Same entry points; owned streams no longer require the `stream` feature |\n| `request(type, &payload)` | `request(TypedMessage::new(type, &payload))` |\n| `stream(type, &payload) -> (id, receiver)` | `stream(TypedMessage::new(type, &payload))`, then `id()` / `into_parts()` |\n| `send(id, type, &payload)` | `send(id, TypedMessage::new(type, &payload))` |\n| `request_raw(flags, body)` | Same call; retains drain state after a nonterminal first reply |\n| `stream_raw(flags, body) -> (id, receiver)` | Owned raw stream, then `id()` / `into_parts()` |\n| `send_raw(id, flags, body)` | Same call, requiring a live owned ID |\n| `connect_with_timeout(path, duration)` | `connect_with(path, |o| o.setup_timeout(duration))` |\n| `connect_stream_with_timeout(stream, duration)` | `connect_stream_with(stream, |o| o.setup_timeout(duration))` |\n| `connect_with_deadline(path, deadline)` | `connect_with(path, |o| o.setup_timeout(deadline.saturating_duration_since(Instant::now())))` |\n| `connect_stream_with_deadline(stream, deadline)` | `connect_stream_with(stream, |o| o.setup_timeout(deadline.saturating_duration_since(Instant::now())))` |\n| `ready()` | `ready().agent` |\n| `ready_bytes()` | `ready().ready_bytes()` |\n| `negotiated_version()` / `supports(type)` | `ready().negotiated_version` / `ready().supports(type)` |\n| `protocol()` / `is_legacy_protocol()` | `ready().wire_format` / `ready().is_legacy_protocol()` |\n| `agent_version()` | `ready().agent_version()` |\n| `ensure_version_compat(type)` | `AgentProtocol::ensure_version_compat_for(type, client.ready().negotiated_version)` |\n| `AgentClient::ensure_version_compat_for(type, generation)` | `AgentProtocol::ensure_version_compat_for(type, generation)` |\n| Unwired `AgentStream<T>` / packet-only `AgentTransport` | Active `AgentStream` / owned byte transports accepted directly by `connect_stream` |\n| Connection close by ownership transfer | Shared `close(&self)`; last client/stream owner also closes the transport |\n\n`ClientError.delivery` distinguishes `NotSent` from `Unknown` after writer admission. Requests are never automatically replayed. Raw stream access, known-ID sends, custom transports, ready bytes, dynamic names, and encoded payloads remain available without adopting SDK domain objects.\n\nDeadline conversions use `tokio::time::Instant`, matching the original absolute-deadline API. Keep the conversion inside the options closure so it uses the remaining duration when setup begins. Known native and encoded sends perform the same availability check automatically; the public `AgentProtocol` helper supports callers retaining only the generation. Unsupported operations now use the shared `ClientError` category rather than the previous agent-specific error fields.\n\nValidation: `cargo test -p microsandbox-agent-client --all-features --locked`. Unix-socket tests require permission to bind temporary local sockets. The migrated historical handshake cases are source-level regression tests; they do not replace live historical runtime/SDK validation.\n\nThe SDK retains an internal optimized client for generation-eight bulk transfers and Unix shared arenas. Its existing bulk APIs remain available; the generic client is the public transport-independent framed API.\n","packages/agent-client/typescript/README.md":"# @microsandbox/agent-client\n\nLow-level TypeScript access to the microsandbox agent protocol. `AgentClient` is `Client<AgentProtocol>` from `@microsandbox/protocol-client`; the agent package supplies relay setup, its existing encoder, and generation gates. The shared engine owns framing, IDs, queues, cancellation, and transport lifetime. Sandbox lifecycle and process/filesystem convenience APIs belong to the SDK.\n\n```text\nAgentProtocol (setup, encoder, gates)\n                 |\n          Client<AgentProtocol>\n            /          \\\n native / encoded     raw / exact packets\n                 |\n       Unix socket / named pipe / WebSocket\n```\n\n## Installation and entry points\n\nInstall with `npm install @microsandbox/agent-client`. Node.js 22 or newer is required for native transports. The root entry is browser-safe. `@microsandbox/agent-client/node` imports Node's `net` module and accepts native endpoint paths verbatim, including Windows named-pipe names.\n\n## Native messages\n\n```ts\nimport { connectUnix } from \"@microsandbox/agent-client/node\";\nimport { typedMessage } from \"@microsandbox/agent-client\";\n\nconst client = await connectUnix(\"/tmp/msb-agent.sock\", { setupTimeoutMs: 10_000 });\ntry {\n  const response = await client.request(typedMessage(\"core.fs.request\", {\n    op: { Stat: { path: \"/etc/os-release\", follow_symlink: true } },\n  }), { requestTimeoutMs: 5_000 });\n  console.log(response.type, response.decodePayload());\n  console.log(client.ready.agentVersion, client.ready.negotiatedVersion);\n} finally {\n  await client.close();\n}\n```\n\n`typedMessage(name, value)` asks the agent codec to encode a native payload. `encodedMessage(name, bytes)` supplies only the application payload; it does not supply an envelope or framed packet. Both paths apply known message gates and flags. Names are open strings, so extensions remain accessible without editing an enum. These methods do not validate an application schema. `requestTyped()` accepts an optional checked request with its own result decoder and requires a terminal response.\n\nThe current TypeScript agent encoder continues to emit generation-five envelopes, including its existing CBOR map and byte-string representations. The ready generation controls feature gates. The supported pre-0.5 relay prologue selects generation-one envelopes and rejects unavailable filesystem/TCP operations before sending. The captured ready frame and unknown fields remain available through `client.ready.frame` and `client.ready.readyBytes`.\n\n## Browser streams\n\n```ts\nimport { AgentClient, WebSocketConnector, typedMessage } from \"@microsandbox/agent-client\";\n\nconst client = await AgentClient.connectConnector(\n  new WebSocketConnector(\"wss://relay.example.com/agent\"),\n  { setupTimeoutMs: 10_000 },\n);\ntry {\n  const stream = await client.openStream(typedMessage(\"core.exec.request\", {\n    cmd: \"sh\", args: [\"-lc\", \"echo hello\"],\n  }));\n  try {\n    for await (const frame of stream) {\n      console.log(frame.type, frame.payload);\n    }\n  } finally {\n    stream.close();\n  }\n} finally {\n  await client.close();\n}\n```\n\nThe relay must forward the agent's binary byte stream, including its prologue. `WebSocketTransport` also wraps a caller-constructed socket for custom authentication. Its incoming queue defaults to 8 MiB and 4096 messages; overflow closes the connection because browser WebSockets cannot pause incoming messages. Outgoing writes wait for the browser's send queue to drain. The connector and handshake share one setup deadline.\n\n## Raw and exact access\n\n```ts\nimport { type AgentClient, TransportPacket } from \"@microsandbox/agent-client\";\n\nasync function exchangeOpaque(client: AgentClient, envelope: Uint8Array) {\n  const stream = await client.openStreamRaw(0, envelope);\n  const { sender, receiver } = stream.split();\n  try {\n    await sender.send(0, envelope);\n    await client.sendRaw(sender.id, 0, envelope);\n    return await receiver.next({ requestTimeoutMs: 5_000 });\n  } finally {\n    receiver.close();\n    sender.close();\n  }\n}\n\nasync function forwardExactPacket(client: AgentClient, bytes: Uint8Array) {\n  await client.writeUnchecked(TransportPacket.fromBytes(bytes));\n}\n```\n\nRaw APIs leave envelope and payload bytes opaque. `requestRaw(flags, body)` returns the first raw reply. `openStreamRaw()` receives through the terminal reply. `sendRaw(id, flags, body)` requires an active ID owned by this connection. A split sender retains its original lease and cannot regain permission if the numeric ID is reused. `writeUnchecked(bytes)` can write even deliberately malformed transport bytes; `TransportPacket.fromBytes()` optionally validates a single packet first.\n\nNative replies retain the complete original raw frame as `frame.raw`, including unknown envelope fields. `core.error` is an ordinary terminal agent response; interpreting it belongs to the caller or a checked request decoder.\n\n## Ownership and failures\n\nStreams own their send and receive halves. `split()` transfers those halves and invalidates the original stream's send/receive methods. Closing a receiver or leaving async iteration stops local delivery; it sends no remote cancel, signal, or EOF. An abandoned ID remains draining until its terminal frame arrives. A timed-out `next()` does not consume the next arriving frame.\n\n`client.clone()` shares the connection. `client.close()` closes every shared handle and wakes pending operations. Explicitly close clients and streams: JavaScript garbage collection only offers best-effort cleanup. A request deadline covers writer admission and the local response wait; it does not cancel remote execution. `ClientError.delivery` is `not_sent` before admission and `unknown` afterward. Nothing automatically retries an admitted operation.\n\n## Migration from the previous package API\n\n| Previous surface | Shared-engine surface |\n| --- | --- |\n| Independent `AgentClient` class/router | `AgentClient = Client<AgentProtocol>` plus convenience constructors |\n| `handshakeTimeoutMs` | `setupTimeoutMs`, covering dial and handshake together |\n| `client.negotiatedVersion()` | `client.ready.negotiatedVersion` |\n| Closed `MessageType` parameter | Open string names; `MessageType` still lists known names |\n| Packet-oriented custom `AgentTransport` | Ordered `read(maxBytes)`, `write(bytes)`, `close()` byte transport |\n| `InboundFrame.fromRawFrame(frame)` | `new AgentEnvelopeCodec().decode(frame)` |\n| `new AgentStream(...)` | Obtain owned streams from `client.openStream()` or `openStreamRaw()` |\n| Request, encoded payload, stream, exact packet methods | Retained on the generic client, with additional raw and split interfaces |\n\nThese are public source API changes. Existing agent wire behavior is preserved; source and byte tests do not replace the required historical runtime/SDK launch tests.\n\n## Development\n\nFrom the repository's `packages` directory, run `npm ci`, `npm run build`, `npm run typecheck`, and `npm test`. The private workspace links the shared package locally; published dependency metadata uses the release version. The README's TypeScript examples are checked with the public package declarations.\n","packages/control-client/rust/README.md":"# Rust control client\n\n`ControlConnection` discovers whether an existing control endpoint supports framed CBOR or legacy JSON. `ControlClient` is the explicitly framed `Client<ControlProtocol>` entry point, and `JsonControlClient` is an explicit legacy unary adapter. Their checked helpers share the same operation records.\n\n```text\nControlConnection -- JSON capabilities --+-- advertised CBOR: fresh stream + hello\n                                        |                      |\n                                        |               ControlClient\n                                        |\n                                        +-- legacy: JsonControlClient\n```\n\nAutomatic discovery has one deadline across the probe, redial, and hello. It selects CBOR only after a valid affirmative capability response. Malformed replies, arbitrary peer errors, empty EOF, and failed framed setup are errors. There is no error-string downgrade or mutation replay. `ControlClient` sends its hello directly when callers already know the peer supports framed control.\n\n```rust,no_run\nuse microsandbox_control_client::{\n    ControlConnection, ControlMessageType, ControlReply, Empty, GetMemoryState, TypedMessage,\n};\n\nasync fn inspect(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {\n    let connection = ControlConnection::connect(path).await?;\n    let reply = connection.request(TypedMessage::new(ControlMessageType::MemoryState, Empty {})).await?;\n    match reply {\n        ControlReply::Framed(message) => println!(\"{}\", message.t),\n        ControlReply::Json(reply) => println!(\"{} original JSON bytes\", reply.raw().len()),\n    }\n    println!(\"{} MiB\", connection.request_typed(&GetMemoryState).await?.target_mib);\n    connection.close().await;\n    Ok(())\n}\n```\n\nIn framed mode, clones reuse one connection. Automatic JSON mode rediscovers before each fresh exchange when its connector cannot prove runtime identity. An owner implementing `VerifiedControlConnector` verifies the connected peer and the runtime's OS birth token on every dial, then rechecks the active run before each operation; `connect_verified_connector` can reuse that owner's JSON selection. A path or PID alone is insufficient. A detected change invalidates the handle before sending instead of silently retargeting the request. The runtime owner, such as an SDK backend, supplies that identity implementation; the protocol package does not claim to verify it itself.\n\n`JsonControlClient::new(path)` and `from_connector(...)` are inert constructors for callers explicitly choosing legacy mode. Its `request` translates only known native requests and returns the actual `JsonReply`, including `ok:false` replies. Checked helpers report `LegacyRemote` with the original reply and unknown batch progress. JSON numbers retain their original tokens; checked memory fields use `u64` without a floating-point intermediate. `connection.framed()` fails locally in JSON mode, and encoded-payload input is rejected there before dialing. Raw streams and exact packets remain available on the explicitly framed client.\n\n`connection.capabilities()` borrows the released generation-1 discovery projection without network I/O, while `connection.runtime_capabilities()` exposes the complete discovered facility set. `GetCapabilities` remains available for a fresh generation-1-compatible observation and `GetRuntimeCapabilities` requests the complete record on generation 2. `closed().await` observes shared closure, including an idle framed disconnect; a successful JSON exchange ending in EOF does not close the session.\n\nGeneration 1 remains the released capabilities, memory, CPU, and secrets surface. Generation 2 adds `CreateCheckpoint`, `CreateDiskCheckpoint`, `CreateBranch`, `PauseRuntime`, `ResumeRuntime`, `GetPauseState`, `GrowRootDisk`, and `CompactDisks`. `ControlConnection` talking to a framed generation-1 runtime selects the historical JSON representation for these checked operations before sending anything; it never retries a mutation after a framed failure. The explicit `ControlClient` instead returns a not-sent unsupported-operation error. Linux descriptor-backed branch creation remains an SDK-level JSON/`SCM_RIGHTS` exception because ordinary framed messages cannot transfer file descriptors.\n\nEnable `uds` for Unix endpoint connections or `named-pipe` for Windows endpoint connections. Caller-owned transports and connectors can be used without either native feature. Endpoint paths are accepted verbatim.\n\n```rust,no_run\nuse microsandbox_control_client::{\n    ControlClient, GetMemoryState, SetMemoryTarget, size::SizeExt,\n};\n\nasync fn resize(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {\n    let client = ControlClient::connect(path).await?;\n    let before = client.request_typed(&GetMemoryState).await?;\n    let accepted = client.request_typed(&SetMemoryTarget::new(2048.mib())).await?;\n    println!(\"{} -> {} MiB\", before.target_mib, accepted.target_mib);\n    client.close().await;\n    Ok(())\n}\n```\n\nMemory wire records retain `u64` values. The existing size helpers retain their conversion rules; direct `SetMemoryTarget { total_mib }` construction provides full-width wire input. Target responses report acceptance and current observations, not guest convergence. Ordered secret batches preserve partial completion and the index of the first failed entry.\n\nThe client keeps `request`, `stream`, `request_raw`, `stream_raw`, explicit-ID `send`/`send_raw`, exact `write_unchecked`, owned `into_parts`, and checked `request_typed` operations. Generic requests return peer error frames as messages; checked helpers interpret them and retain the original response. No sandbox, execution, filesystem, or convergence service is required to use any of these paths.\n\n```rust,no_run\nuse microsandbox_control_client::{ControlClient, EncodedMessage};\n\nasync fn inspect(client: &ControlClient, payload: Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {\n    let response = client.request(EncodedMessage::new(\"extension.inspect\", payload)).await?;\n    println!(\"{}: {} payload bytes\", response.t, response.p.len());\n    Ok(())\n}\n```\n\nSetup defaults to one ten-second deadline and requests to a thirty-second local wait. Request expiry does not cancel remote work or trigger replay. `ClientError` retains `NotSent` versus `Unknown` delivery. Clones and owned streams share a connection; `close` closes it for all owners.\n\nRun `cargo test -p microsandbox-control-client --all-features --locked`. For live tests, set `MSB_CONTROL_TEST_SOCKET` and `MSB_CONTROL_TEST_MODE=cbor` or `json`. The explicitly framed test requires CBOR; run only `live_automatic_discovery_and_explicit_json -- --ignored --exact` against a JSON-only runtime. Use bounded disposable fixtures (512 MiB and two CPUs), and run live tests serially. The automatic test changes targets and restores them through explicit JSON. Optional `MSB_CONTROL_TEST_SECRET` names a dummy fixture initially set to `before` and allowed for `example.invalid`; the test exercises legacy partial-failure reporting and restores that fixture. Skipped live tests are not runtime or platform validation.\n","packages/control-client/typescript/README.md":"# TypeScript control client\n\n`ControlClient` is `Client<ControlProtocol>` over the shared framed engine. It sends a bounded `control.hello` directly, negotiates generation and limits, and exposes the original welcome frame. Use it when the peer is known to support framed control. It does not probe JSON or downgrade after failure.\n\nThe root entry is browser-safe and accepts a caller-owned byte transport or reconnectable connector. The `/node` entry accepts an existing Unix socket or Windows named-pipe path without renaming it.\n\n```ts\nimport { GetMemoryState, MiB, SetMemoryTarget, typedMessage } from \"@microsandbox/control-client\";\nimport { connectFramedControl } from \"@microsandbox/control-client/node\";\n\nasync function inspectAndResize(path: string) {\n  const client = await connectFramedControl(path, { setupTimeoutMs: 10_000 });\n  try {\n    const response = await client.request(typedMessage(\"control.memory.state\", {}));\n    console.log(response.type, response.raw.body);\n    const before = await client.requestTyped(new GetMemoryState());\n    const accepted = await client.requestTyped(new SetMemoryTarget(MiB(2048)));\n    console.log(before.target_mib, accepted.target_mib); // bigint, even for small values\n  } finally {\n    await client.close();\n  }\n}\n```\n\nGeneration-1 checked helpers cover capabilities, memory state/target, CPU state/target, and ordered secret updates. Generation 2 adds `GetRuntimeCapabilities`, `CreateCheckpoint`, `CreateDiskCheckpoint`, `CreateBranch`, `PauseRuntime`, `ResumeRuntime`, `GetPauseState`, `GrowRootDisk`, and `CompactDisks`. They validate replies and retain the actual response on `ControlClientError`. Generic message requests return peer error frames directly. A target reply reports acceptance and observation; it does not establish guest convergence. Secret batches stop at the first failure and retain earlier completed changes.\n\nUse `connectControl` for automatic discovery on the same endpoint. It sends the existing read-only JSON capabilities request, closes that exchange, and opens a fresh framed connection only after an affirmative CBOR advertisement. Setup has one total deadline across both connections. Malformed discovery, timeout, EOF without a reply, or a failed welcome never trigger fallback or replay.\n\n```text\nconnectControl(path)\n    |\n    +-- JSON capabilities --> valid legacy reply --> fresh JSON exchange per operation\n    |\n    +-- JSON capabilities --> advertises CBOR -----> fresh hello/welcome --> shared framed client\n```\n\n```ts\nimport { GetMemoryState, MiB, SetMemoryTarget, typedMessage } from \"@microsandbox/control-client\";\nimport { connectControl } from \"@microsandbox/control-client/node\";\n\nasync function compatibleResize(path: string) {\n  const connection = await connectControl(path);\n  try {\n    const reply = await connection.request(typedMessage(\"control.memory.state\", {}));\n    if (reply.kind === \"cbor\") console.log(reply.frame.raw.body);\n    else console.log(reply.reply.raw, reply.reply.value.get(\"memory\"));\n    const before = await connection.requestTyped(new GetMemoryState());\n    const accepted = await connection.requestTyped(new SetMemoryTarget(MiB(2048)));\n    return { before, accepted };\n  } finally {\n    await connection.close();\n  }\n}\n```\n\n`ControlConnection.connectConnector` accepts a repeatable caller connector from the browser-safe root. The connection exposes the read-only generation-1 `capabilities` projection, the complete `runtimeCapabilities` discovery snapshot, `mode`, `clone()`, `isClosed()`, `close()`, `request()`, and `requestTyped()`. `framed()` returns the complete generic surface in CBOR mode and fails locally with `unsupported_mode` in JSON mode. Encoded messages likewise fail locally in JSON mode; the adapter translates only known, validated native operations.\n\nWhen a framed peer selects generation 1, `ControlConnection.requestTyped()` sends generation-2 checked operations through their historical JSON form before any mutation is admitted. An unverified connector rediscovers the endpoint format first. This path is never a retry after a CBOR failure. The explicit `ControlClient` rejects those operations locally against a generation-1 welcome. Linux descriptor-backed branch creation remains outside ordinary CBOR because it requires `SCM_RIGHTS`; generation 2 covers direct local branching without descriptor transfer.\n\n`JsonReply` contains the original response line, including its delimiter and whitespace, and a lossless `Map` of fields. `JsonNumber.token` retains each original numeric token; checked memory observations become `bigint` without passing through a JavaScript number. Ordinary requests return `ok:false` replies. Checked helpers raise `legacy_remote` with the original reply and unknown batch progress. Neither arbitrary diagnostic strings nor legacy errors are converted into structured CBOR errors.\n\nFor explicit legacy access, construction is inert and performs no discovery:\n\n```ts\nimport { GetCpuState } from \"@microsandbox/control-client\";\nimport { jsonControl } from \"@microsandbox/control-client/node\";\n\nasync function inspectLegacy(path: string) {\n  const client = jsonControl(path);\n  try {\n    return await client.requestTyped(new GetCpuState());\n  } finally {\n    await client.close();\n  }\n}\n```\n\nThe equivalent root API is `JsonControlClient.fromConnector(connector, options?)`. Ordinary legacy request and response lines keep their existing size contract; only automatic discovery replies have a 64 KiB limit. Preparing a secret batch snapshots its entries without imposing the framed 4 MiB ceiling on JSON. JavaScript strings remain subject to garbage collection; explicit buffer clearing is not a guarantee that all copies have been erased.\n\nA standalone automatic JSON connection rediscovers before each fresh operation because an endpoint path cannot establish process identity. `ControlConnection.connectVerifiedConnector` permits discovery reuse when the caller supplies a `VerifiedControlConnector`: `connect` must verify every connected peer against the saved OS process birth identity, and `verifySession` must check the active run and process before each operation. Both receive a deadline and cancellation context. Runtime replacement raises `runtime_changed` before mutation admission and closes the shared handle. This extension point does not implement an SDK database or OS identity verifier by itself.\n\n`MiB`, `GiB`, `KiB`, `TiB`, and `Mebibytes` are exported from `@microsandbox/types/size`. Their arithmetic matches the SDK helpers. `SetMemoryTarget` validates integer precision and range before converting to the wire quantity; it also accepts `bigint` for full-width values.\n\nAll generic operations remain available:\n\n| Need | Method |\n|---|---|\n| Native or encoded payload | `request(typedMessage(...))`, `request(encodedMessage(...))` |\n| Exact opaque envelope | `requestRaw(flags, body)` |\n| Message or raw subscription | `openStream(...)`, `openStreamRaw(...)` |\n| Owned sender and receiver | `stream.split()` |\n| Follow-up using an owned ID | `sendOnStream(id, ...)`, `sendRaw(id, ...)` |\n| Exact packet without a subscription | `writeUnchecked(bytes)` |\n| Optional checked unary operation | `requestTyped(request)` |\n| Shared connection ownership | `clone()`, `close()` |\n\n```ts\nimport { ControlClient, encodedMessage, type ByteTransport } from \"@microsandbox/control-client\";\n\nasync function inspectExtension(transport: ByteTransport, payload: Uint8Array) {\n  const client = await ControlClient.connectTransport(transport);\n  try {\n    const reply = await client.request(encodedMessage(\"extension.inspect\", payload));\n    return { name: reply.type, payload: reply.payload, original: reply.raw };\n  } finally {\n    await client.close();\n  }\n}\n```\n\nThe default control request deadline is 30 seconds. Expiry abandons the local wait; it does not cancel remote work or retry it. `ClientError.delivery` distinguishes requests that were not admitted from unknown outcomes. Complete idle connections do not expire automatically. Explicit close closes every clone; JavaScript finalization is only best-effort cleanup.\n\nFrom `packages`, run `npm run build`, `npm run typecheck`, and `npm test`. Live validation is deliberately opt-in: set `MSB_CONTROL_TEST_SOCKET` to a disposable framed runtime's socket and run `npm test -w @microsandbox/control-client`. Add `MSB_CONTROL_TEST_MODE=cbor` to run automatic discovery as well. The package serializes test files because live files mutate and restore the same VM's targets. A skipped live test is not platform or runtime compatibility evidence.\n\nFor a historical JSON-only runtime, set `MSB_CONTROL_TEST_MODE=json` and run only the compatibility live file with `npm exec -w @microsandbox/control-client -- vitest run tests/compatibility-live.test.ts`. It checks automatic and explicit JSON access, concurrent reads, full-width result types, and accepted memory/CPU targets, then restores the original targets. These running-runtime tests do not establish SDK launch compatibility.\n\nThe additional secret test requires `MSB_CONTROL_TEST_SECRET` naming a dummy fixture initially set to `before` and allowed for `example.invalid`. It exercises partial completion, malformed batches, and legacy JSON failures, then restores those fixture values.\n","packages/microsandbox-types/README.md":"# microsandbox types\n\nShared task and wire contract types for microsandbox.\n\nThese packages define the backend-neutral shapes that describe what a sandbox should be: its rootfs source, resources, mounts, patches, network, lifecycle, and the cloud HTTP request/response bodies that carry those specs over the wire. Everything that has to agree on a sandbox's shape (the Rust SDK, the CLI, the cloud API, and the TypeScript front end) depends on these contracts instead of redefining them.\n\nThis is a contract layer, not a runtime. It does not create, start, or talk to sandboxes. It only models the data those operations exchange.\n\n## Layout\n\n```text\npackages/microsandbox-types/\n├── rust/\n└── typescript/\n```\n\n- `rust/` publishes as `microsandbox-types` (crate name `microsandbox_types`). It is the source of truth for every shared type.\n- `typescript/` publishes as `@microsandbox/types`. Its `src/cloud.ts` (entry) and `src/domain.ts` are generated from the Rust types, never hand-edited; only the cloud contract and the domain types it references are emitted.\n\n## Source Of Truth\n\nThe Rust crate owns the definitions. The TypeScript bindings are derived from them with [`ts-rs`](https://github.com/Aleph-Alpha/ts-rs) behind the crate's `ts` feature, so the two stay byte-for-byte aligned.\n\n```text\nrust/lib/*.rs  ──(ts-rs)──▶  typescript/src/{cloud,domain}.ts\n```\n\nTo regenerate the bindings after changing a Rust type:\n\n```bash\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate\n```\n\nCI runs the same generator with `--check` and fails when the checked-in bindings drift:\n\n```bash\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate -- --check\n```\n\n## What Lives Here\n\n- Sandbox specs: `SandboxSpec`, `SandboxResources`, `SandboxRuntimeOptions`, rootfs sources, mounts, patches, init, lifecycle policy.\n- Networking intent: `NetworkSpec`, published ports, protocols.\n- Volumes and snapshot manifests: `VolumeSpec`, `SnapshotSpec`, and their kinds.\n- Exec and logging: `Rlimit`, `RlimitResource`, `LogSource`, `SandboxLogLevel`.\n- Cloud wire contracts: the source-tagged `CloudCreateSandboxRequest` union,\n  `CloudSandbox`, snapshot resources and operations, and\n  paginated/message/error bodies.\n\nCloud snapshot contracts distinguish the durable resource from the asynchronous\ncapture operation:\n\n- `CloudCreateSnapshotRequest` is discriminated by `kind`. Its current `disk`\n  variant captures the writable disk, and the enum can gain other snapshot\n  kinds without changing the envelope shape.\n- `CloudSnapshot` uses the same kind discriminator and includes its canonical\n  manifest, byte size, labels, and `CloudSnapshotLocation`.\n- `CloudSnapshotLocation` is either a managed artifact ID or a host-volume\n  path. The same type is used when restoring a sandbox so location semantics\n  are not duplicated.\n- `CloudSnapshotOperation` carries the requested kind and tracks capture\n  through `queued`, `in_progress`, `succeeded`, or `failed`; `result` is\n  populated on success.\n- Validation helpers: sandbox-name and hostname rules shared across SDK, CLI, and cloud.\n\nBackend-private materialized state (registry credentials, local cache paths, DB rows, resolved manifest digests, process handles) deliberately stays out of these packages. See each language's README for details.\n\n## Cloud Request Compatibility\n\nCloud request objects ignore unknown fields, including fields inside network and runtime options. This allows newer SDKs to send additive options before the receiving Cloud release understands them. Unknown settings have no effect until that server supports them; request success does not confirm that every supplied setting was applied.\n\nMissing fields retain their documented defaults. Required fields, recognized field types and values, and enum variants still undergo validation. Ignoring unknown object keys does not make new enum variants, renamed fields, or changed field types compatible.\n\nThis policy applies to public Cloud wire types, not host or operator configuration. SDK checks for runtime-only options remain separate from the server's treatment of future Cloud fields.\n","packages/microsandbox-types/rust/README.md":"# microsandbox-types\n\nShared task and wire contract types for microsandbox.\n\nThis crate is the source of truth for the backend-neutral shapes that describe a sandbox and the cloud HTTP bodies that carry them. The Rust SDK, the CLI, and the cloud backend all import these types so they agree on one definition instead of duplicating wire shapes. The generated `@microsandbox/types` TypeScript package is derived from this crate.\n\nIt is a leaf dependency by design. It pulls in `serde`, `serde_json`, `chrono`, `sha2`, and `thiserror`, and nothing from the local VM machinery (no runtime, image, network, or database crates). That keeps it cheap enough for front-end generation, cloud API models, and SDK wrappers to all depend on.\n\n## What This Crate Owns\n\nThe crate models durable user and wire intent: what the user wants to exist, not how a backend fulfills it.\n\n- **Sandbox spec** (`domain` module): `SandboxSpec`, `SandboxResources`, `SandboxRuntimeOptions`, `EnvVar`, `SandboxPolicy`.\n- **Rootfs sources**: `RootfsSource` (bind, OCI, disk image), `OciRootfsSource`, `DiskImageFormat`, `PullPolicy`.\n- **Mounts and patches**: `VolumeMount`, `MountOptions`, `StatVirtualization`, `HostPermissions`, `Patch`, `SecurityProfile`.\n- **Volumes and snapshots**: `VolumeSpec`, `VolumeKind`, `NamedVolumeCreate`, `NamedVolumeMode`, `SnapshotSpec`, and the canonical `SnapshotManifest` schema.\n- **Networking**: `NetworkSpec`, `PublishedPortSpec`, `PortProtocol`.\n- **Exec and logs**: `Rlimit`, `RlimitResource`, `SandboxLogLevel`, `LogSource`, `HandoffInit`.\n- **Cloud wire contracts** (`cloud` module): sandbox, snapshot, pagination, message, and error request/response types.\n- **Validation** (`validation` module): `validate_sandbox_name`, `validate_hostname`, `hostname_from_sandbox_name`, and the `MAX_SANDBOX_NAME_BYTES` / `MAX_HOSTNAME_BYTES` limits.\n\nBackend-private materialized state stays out: registry credentials, local CA paths, replace flags, pull-discovered manifest digests, snapshot upper paths, process handles, and DB rows belong to the SDK and backends, not the contract.\n\n## Usage\n\n```toml\n[dependencies]\nmicrosandbox-types = \"0.7.0\"\n```\n\n```rust\nuse microsandbox_types::{RootfsSource, SandboxResources, SandboxSpec};\n\nlet spec = SandboxSpec {\n    name: \"worker\".into(),\n    image: RootfsSource::oci(\"python\"),\n    resources: SandboxResources { cpus: 2, memory_mib: 1024 },\n    ..Default::default()\n};\n```\n\nThe Rust SDK re-exports the contract types it accepts, so most SDK users get these through `microsandbox::*` and do not depend on this crate directly.\n\n## Serialization Notes\n\n- `SandboxSpec`, `SandboxResources`, `SandboxRuntimeOptions`, `NetworkSpec`, and `MountOptions` use `#[serde(default)]`, so partial JSON fills missing fields from static defaults.\n- Lowercase-on-the-wire enums (`StatVirtualization`, `HostPermissions`, `SecurityProfile`, `SandboxLogLevel`, `LogSource`, `PortProtocol`) match the CLI grammar and the TypeScript string unions.\n- `VolumeMount` has hand-written `Serialize`/`Deserialize`. It tags variants with a `type` field and accepts a legacy top-level `readonly` flag, folding it into `MountOptions` on read.\n- Static defaults only. Nothing here reads process-global or profile config; the SDK and backends apply environment defaults before execution.\n\n## TypeScript Generation\n\nThe `ts` feature derives `ts_rs::TS` on exported wire types and builds the `microsandbox-types-generate` binary, which writes `domain.ts`, `snapshot.ts`, and `cloud.ts` beneath `../typescript/src`.\n\n```bash\n# Regenerate the checked-in bindings.\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate\n\n# Verify they are current (used in CI; exits non-zero when stale).\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate -- --check\n```\n\nA unit test (`checked_in_bindings_match_generated_output`) also fails when any generated TypeScript file drifts, so `cargo test --features ts` catches stale bindings too.\n\n## Testing\n\n```bash\ncargo test -p microsandbox-types\ncargo test -p microsandbox-types --features ts\n```\n","packages/microsandbox-types/typescript/README.md":"# @microsandbox/types\n\nShared task and wire contract types for microsandbox, as TypeScript.\n\nThis package is the type-only mirror of the cloud contract in the `microsandbox-types` Rust crate. It gives TypeScript consumers (the cloud front end and anything talking to the cloud API) the exact cloud wire shapes microsandbox uses, so a `CloudSandboxSpec` means the same thing on both sides of the wire. Only the domain types the cloud contract references are generated; the broader SDK/domain surface is not.\n\nIt contains no runtime code. Every export is a type; importing it adds nothing to your bundle.\n\n## Generated, Not Hand-Written\n\nThree files are generated from the Rust crate with [`ts-rs`](https://github.com/Aleph-Alpha/ts-rs), each carrying a `// @generated` header. Do not edit them:\n\n- `src/cloud.ts` — the package entry: the cloud wire types, which import and re-export their domain and snapshot dependencies.\n- `src/domain.ts` — only the domain types the cloud twins transitively reference (`EnvVar`, `Rlimit`, `NetworkPolicy`, …).\n- `src/snapshot.ts` — the canonical snapshot manifest schema embedded by cloud snapshot resources.\n\nTo change a shape, edit the Rust type and regenerate from the repo root:\n\n```bash\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate\n```\n\n## Install\n\n```bash\nnpm install @microsandbox/types\n```\n\n## Usage\n\nImport the shapes you need with `import type`:\n\n```ts\nimport type {\n  CloudCreateSandboxRequest,\n  CloudSnapshot,\n  CloudSnapshotOperation,\n} from \"@microsandbox/types\";\n\nfunction createSandbox(request: CloudCreateSandboxRequest) {\n  // POST the source-tagged request to the cloud API\n}\n```\n\n`CloudCreateSnapshotRequest` and `CloudSnapshot` are unions discriminated by\n`kind`; the current public variant is `\"disk\"`. `CloudSnapshotOperation` carries\nthat same kind and exposes only the public statuses `queued`, `in_progress`,\n`succeeded`, and `failed`. Both snapshot creation results and disk-snapshot\nrestore requests use `CloudSnapshotLocation` to distinguish a managed artifact\nID from a host-volume path.\n\n## Generated Shape Notes\n\nThe bindings follow `ts-rs` conventions, which mirror the Rust serde representation:\n\n- Cloud enums are internally tagged with a `type` field: `CloudRootfsSource` is `{ type: \"bind\"; … } | { type: \"oci\"; reference: string } | { type: \"disk_image\"; … }`, and `CloudVolumeMount` / `CloudHostPattern` / `CloudViolationAction` follow the same shape.\n- `CloudCreateSandboxRequest` is a flat union discriminated by `source: \"oci\" | \"bind\" | \"disk_image\" | \"disk_snapshot\"`. Common sandbox fields stay flat on every variant; source-specific fields such as `reference`, `path`, and `disk_snapshot_ref` appear only where they apply. Servers also accept the legacy `image` request shape without `source` during migration.\n- `CloudCreateSnapshotRequest` and `CloudSnapshot` are flat unions discriminated by the current `kind: \"disk\"`; common snapshot fields stay flat beside the discriminator.\n- Lowercase domain enums like `StatVirtualization` are string-literal unions (`\"strict\" | \"relaxed\" | \"off\"`).\n- Optional Rust fields are `T | null`; fields skipped when absent are `?:` optional.\n- Referenced domain and snapshot types live in `domain.ts` and `snapshot.ts` and are re-exported from the package entry, so a single import from `@microsandbox/types` sees the whole cloud contract.\n\n## Build And Typecheck\n\n```bash\nnpm run build       # tsc -> dist/\nnpm run typecheck   # tsc --noEmit\n```\n","packages/protocol-client/rust/README.md":"# Generic framed client\n\n`Client<P>` owns one byte connection, its correlation IDs, and all routed subscriptions. `P: Protocol` establishes the transport and selects message metadata and an `EnvelopeCodec`. Protocol consumers can implement both traits outside this crate. `tests/engine.rs` exercises external protocol setup and routing, while `tests/external_codec.rs` supplies an external connector and a non-CBOR envelope codec across native, encoded, raw, checked, stream, explicit-ID, and exact-packet calls. Rust native payload serialization remains CBOR; the custom codec controls its enclosing envelope, and encoded/raw paths retain caller-supplied bytes.\n\n```text\nnative payload ---- TypedMessage ---+\nencoded payload --- EncodedMessage -+--> codec --> shared writer\nraw envelope -----------------------+-----------> shared writer\nexact packet -----------------------------------> shared writer\n\nshared reader --> ID routing --> RawStream --> Stream (decode on recv)\n```\n\n`request` returns the first response. If it is not terminal, the router retains the ID and discards later replies until terminal completion. `request_typed` additionally requires a terminal response and invokes a borrowed `Request<P>` implementation's checked decoder. Application errors remain that implementation's responsibility; the generic engine does not equate a terminal response with success.\n\n`stream` and `stream_raw` return owned subscriptions. `into_parts()` moves them into a cloneable sender and one receiver. Both retain the connection. Dropping or closing the receiver disables sends and drains any admitted operation until terminal completion; it does not send a process signal, file EOF, TCP close, or cancellation packet. Old sender handles cannot gain permission when an ID is reused.\n\n`send` and `send_raw` support explicit IDs owned by the connection. `write_unchecked` serializes caller-owned packet bytes without creating an ID or response subscription. Native and encoded-payload paths apply protocol availability gates; raw paths preserve opaque bytes. A decoded `Message` retains its original `RawFrame`, including unknown envelope fields.\n\nConnections are shared handles. Explicit `close(&self)` closes every handle and wakes waiters. `closed().await` observes closure, including idle peer disconnection, without polling; canceling that wait leaves the connection alone. Last-owner drop closes the transport; the reader and writer do not keep the owner alive. A stream remains an owner after a temporary client handle is dropped.\n\nQueues have item and combined byte limits. A retained slow receiver can backpressure the whole connection; fair per-stream flow control is not provided by the wire protocol. Dropping that receiver releases its queued frames and wakes blocked routing. Active and draining IDs both consume in-flight capacity.\n\n`ClientError.delivery` distinguishes `NotSent` before writer admission from `Unknown` afterward. Timeouts and dropped futures stop local waiting, without retrying or undoing peer work. An EOF before terminal completion is an error; truncation inside a frame is reported separately. Optional incomplete-frame deadlines start at the first byte and do not expire idle connections.\n\nThe `uds` and `named-pipe` features enable `LocalConnector` on their platforms. It uses the endpoint supplied by the caller without inventing names. A custom `Connector` receives an absolute setup deadline and returns an exclusively owned byte stream. `connect_stream` accepts a transport that the caller has already dialed and authenticated.\n\nValidation: `cargo test -p microsandbox-protocol-client --all-features --locked`. The Unix connector test binds a temporary socket and therefore needs an execution environment that permits local socket creation.\n\nProtocols choose whether completed correlation IDs may be reused. Agent connections retire IDs for the lifetime of the connection, matching the relay; control connections may reuse an ID after its terminal response. Exhaustion is a local error and does not reconnect or replay a request.\n","packages/protocol-client/typescript/README.md":"# @microsandbox/protocol-client\n\nA protocol-neutral framed client for TypeScript. `Client<P>` owns one byte transport, correlation IDs, bounded queues, a serialized writer, and response routing. `P` owns setup, its envelope codec, and message gates. Agent and control operations stay in their protocol packages; sandbox lifecycle stays in the SDK.\n\n```text\n                 Client<P>\n          /          |           \\\n native payload   encoded      raw frame / exact packet\n          \\          |           /\n          protocol codec + shared router\n                       |\n                ByteTransport\n```\n\n## Implementing a protocol\n\nAn external package can implement setup and metadata using only public declarations. This example protocol reads one ready byte and uses the shared CBOR envelope. Its server must implement that same handshake.\n\n```ts\nimport {\n  Client, CborEnvelopeCodec, readExactly,\n  type Protocol, type ByteTransport, type EstablishContext, type Established, type SendMetadata,\n} from \"@microsandbox/protocol-client\";\n\nclass ExampleProtocol implements Protocol<number> {\n  async establish(transport: ByteTransport, context: EstablishContext): Promise<Established<number>> {\n    const ready = (await readExactly(transport, 1, context.signal))[0]!;\n    return {\n      transport, ready, codec: new CborEnvelopeCodec(), limits: context.limits,\n      ids: { start: 1, endExclusive: 2 ** 32 },\n    };\n  }\n  prepare(ready: number, _wireName: string): SendMetadata {\n    return { generation: ready, flags: 0 };\n  }\n}\n\nasync function connect(transport: ByteTransport) {\n  return Client.connectTransport(transport, new ExampleProtocol(), { setupTimeoutMs: 10_000 });\n}\n```\n\n`ByteTransport.read(maxBytes)` returns between one and `maxBytes` ordered bytes, or `null` for clean EOF. The engine makes one read at a time and independently serializes writes. `close()` must wake both directions. Passing a transport transfers ownership, including when setup fails. `Connector.connect(context)` supplies repeatable dialing with a deadline and abort signal; it does no discovery itself. `LocalConnector` and `NodeTransport` live at `@microsandbox/protocol-client/node`. Browser-safe `WebSocketConnector` and `WebSocketTransport` are root exports.\n\n`EnvelopeCodec.encodePayload()` supports protocol-specific native encoders. `encode()` wraps supplied payload bytes; `decode()` returns an inspectable `InboundFrame` retaining its `raw` frame. The standard codec uses `{ v, t, p }`, with `p` an actual CBOR byte string, strict unsigned integers, and open message names. Raw subscriptions never invoke that codec.\n\n## Public access levels\n\n| API | Input and result |\n| --- | --- |\n| `request(typedMessage(name, value))` | Native payload; first `InboundFrame` reply |\n| `request(encodedMessage(name, bytes))` | Pre-encoded application payload; first message reply |\n| `requestTyped(request)` | Optional checked request and result decoder; requires terminal reply |\n| `openStream(message)` | Owned native stream through terminal |\n| `requestRaw(flags, body)` / `openStreamRaw(flags, body)` | Opaque envelope bodies and raw replies |\n| `sendOnStream(id, message)` / `sendRaw(id, flags, body)` | Explicit ID owned by this connection |\n| `writeUnchecked(bytes)` | Exact transport bytes without framing or semantic checks |\n| `stream.split()` | Separate owned sender and single receiver |\n| `client.ready` / `clone()` / `close()` | Protocol metadata and shared connection lifetime |\n\nChecked helpers are optional; neither native messages nor raw responses are narrowed to a generated message enum. Application errors remain distinct from `ClientError`. Unknown message names, payload bytes, and envelope fields remain inspectable.\n\n## Limits, ownership, and cancellation\n\nDefault engine limits are 4 MiB per frame, 1024 in-flight exchanges, 256 queued writes, 1024 queued responses per exchange, and 8 MiB of combined engine-owned packet buffering. Protocol setup can select tighter limits. Counts exclude caller-owned inputs, returned frames, and operating-system or browser socket buffers. Native transports read in paused mode; the WebSocket adapter separately caps its input queue and closes on overflow.\n\nA setup deadline covers dialing and protocol establishment. Optional request deadlines cover writer admission and local response waiting; incomplete-frame deadlines start only when a frame begins. Idle connections have no implicit incomplete-frame deadline. `AbortSignal` cancels local waiting. `delivery: \"not_sent\"` means an attempt never entered the writer; `\"unknown\"` means it may have reached the peer. The engine never retries an admitted operation.\n\nClosing a stream only stops local delivery. Nonterminal abandoned exchanges retain their IDs until terminal; stale split senders remain invalid after ID reuse. A receive timeout leaves future frames available. Shared `client.close()` wakes every handle and closes the transport. Close explicitly in `finally`: JavaScript finalization is best-effort, while explicit close is deterministic.\n\n## Development\n\nFrom `packages`, run `npm ci`, `npm run build`, `npm run typecheck`, and `npm test`. Tests exercise an externally defined protocol, full-width IDs, byte preservation, routing, cancellation and admission, ownership, backpressure, transport truncation, WebSocket bounds, and public README examples. Live runtime and historical SDK compatibility tests are additional release requirements.\n\nTo check package exports independently of workspace resolution, install locally packed client packages into a separate consumer directory, then run `node protocol-fixtures/check-packed-protocol-consumer.mjs /path/to/consumer` from `packages`. The consumer lockfile must identify the protocol-client tarball and integrity, and the installed package must not be a workspace symlink. This compiles and executes a custom connector, non-CBOR envelope codec, native/encoded/raw/checked calls, full-width IDs, split streams, explicit sends, and exact packet writes against the installed declarations.\n\nThe command prints a retained output directory. With the packed agent and control packages also installed in that consumer, pass that output directory to `node protocol-fixtures/check-browser-package-roots.mjs /path/to/consumer/protocol-consumer-output`. This bundles all three root exports for the browser, rejects Node builtin imports, and executes the same protocol consumer without Node globals. It verifies browser package loading; actual browser/WebSocket integration remains a separate check.\n\nProtocols choose whether completed correlation IDs may be reused. Agent connections retire IDs for the lifetime of the connection, matching the relay; control connections may reuse an ID after its terminal response. Exhaustion is a local error and does not reconnect or replay a request.\n"},"files":{".agents/skills/microsandbox-docs/SKILL.md":"---\nname: microsandbox-docs\ndescription: Edit Microsandbox product, CLI, and SDK documentation with verified behavior, concise examples, and the repository's Mintlify conventions. Use for changes under microsandbox/docs.\n---\n\n# Microsandbox docs\n\nFollow the repository's `AGENTS.md` for contribution rules and SDK reference structure. Read the affected pages and check the working tree before editing. If a target branch is specified, start from its latest commit and preserve unrelated branch changes.\n\n## Verify the feature\n\n- Check implementation, configuration schema, CLI flags, and supported SDKs before naming an option or writing an example. Distinguish per-secret settings from sandbox-wide defaults and secret-policy denials from ordinary network denials.\n- Check the published tag or release before attaching a version pill. A merged change is not evidence that a tagged version contains it. State local-only or cloud limitations only where they affect the documented workflow.\n- Make examples internally complete. When an example expects a JSON HTTP denial response, show the response enablement, JSON format, and applicable message setting together, even if some are current defaults.\n\n## Write the page\n\n- Lead with what the reader can do and the shortest useful example. Put the main code sample on the page; use accordions for limits and secondary details.\n- Use short headings that name the operation. Keep violation actions, host permissions such as placeholder passthrough, and response-message configuration conceptually separate.\n- Use `CodeGroup` for supported language examples when a workflow spans SDKs. Show a CLI example only when an equivalent flag or command exists. Put `sandbox.yml` in a YAML configuration section instead of presenting it as a CLI command.\n- Keep prose independent of language-specific syntax when possible. Put language or CLI details beside their example or in the relevant reference page. Preserve meaningful edge cases without repeating them across pages.\n\n## Validate and deliver\n\n- Run `python3 scripts/check-docs-language-order.py` and `git diff --check`. Parse changed YAML examples, check links and anchors, and load the affected pages in the Mintlify preview when available.\n- Report the changed pages, checks, and any behavior or release ambiguity. Follow the user's requested target branch and publishing route.\n","AGENTS.md":"# AGENTS.md\n\nThese instructions are only for agents contributing changes to this repository.\nDo not apply them to other repositories or to general agent behavior outside this contribution workflow.\n\n## Scope\n\n- Follow these guidelines when editing code, docs, tests, examples, CI, or release files for microsandbox.\n- Prefer repository conventions over generic agent habits. When unsure, inspect nearby files and match their style.\n- Do not create branches, commit, push, tag, publish, or open pull requests unless the human explicitly asks.\n- Check `git status --short --branch` before making changes. Do not overwrite or revert user work unless explicitly asked.\n\n## Project Map\n\n- `sdk/rust` is the public Rust SDK crate.\n- `crates/cli` contains the `msb` CLI.\n- `crates/runtime` contains VM runtime integration.\n- `crates/filesystem`, `crates/image`, `crates/network`, `crates/db`, `crates/migration`, `crates/metrics`, `crates/metrics-collector`, `crates/protocol`, and `crates/utils` are shared internal crates.\n- `packages/agent-client` and `packages/microsandbox-types` are the shared agent-protocol client and wire-contract type packages, each with Rust and TypeScript implementations.\n- `crates/agentd` is the in-guest agent. It is a workspace member; the musl guest binary that ships in releases is built separately.\n- `sdk/python`, `sdk/node-ts`, and `sdk/go` contain the language SDKs and native bindings.\n- `docs/` contains the documentation site. Keep docs in sync with user-facing behavior.\n- `examples/` contains runnable examples. Add new example projects only when requested or clearly required by the contribution.\n- `mcp/` and `skills/` are submodules related to agent integrations.\n- `vendor/libkrunfw` is a submodule for the kernel firmware library.\n\nRepository layout:\n\n```text\n.\n|-- AGENTS.md\n|-- Cargo.lock\n|-- Cargo.toml\n|-- COMPATIBILITY.md\n|-- DEVELOPMENT.md\n|-- Dockerfile.agentd\n|-- justfile\n|-- msb-entitlements.plist\n|-- assets/\n|-- crates/\n|   |-- agentd/\n|   |-- cli/\n|   |-- db/\n|   |-- filesystem/\n|   |-- image/\n|   |-- metrics/\n|   |-- metrics-collector/\n|   |-- migration/\n|   |-- network/\n|   |-- protocol/\n|   |-- runtime/\n|   |-- testing/\n|   |   |-- init/\n|   |   |-- macros/\n|   |   `-- utils/\n|   `-- utils/\n|-- docs/\n|   |-- changelog/\n|   |-- cli/\n|   |-- getting-started/\n|   |-- images/\n|   |-- networking/\n|   |-- observability/\n|   |-- recipes/\n|   |-- sandboxes/\n|   |-- sdk/\n|   `-- security/\n|-- examples/\n|   |-- python/\n|   |-- rust/\n|   `-- typescript/\n|-- mcp/\n|   |-- bin/\n|   |-- src/\n|   `-- package.json\n|-- packages/\n|   |-- agent-client/\n|   `-- microsandbox-types/\n|-- packaging/\n|   `-- docker/\n|-- scripts/\n|   `-- smoke/\n|-- sdk/\n|   |-- go/\n|   |-- node-ts/\n|   |-- rust/\n|   `-- python/\n|-- skills/\n|   `-- microsandbox/\n`-- vendor/\n    `-- libkrunfw/\n```\n\n## Design Principles\n\n- Before making or continuing a change that may introduce a regression, breaking change, or backward-compatibility risk identified below, stop and alert the human with the likely impact and affected workflows.\n- Keep changes narrowly scoped to the requested behavior. Avoid drive-by refactors, unrelated formatting, or dependency churn.\n- Treat sandbox isolation, host filesystem access, networking, and secret handling as security-sensitive. Validate inputs at boundaries and avoid exposing host paths, credentials, or ambient privileges.\n- For public APIs, keep the Rust SDK, CLI, Python SDK, Node SDK, Go SDK, docs, and examples consistent when they describe the same capability.\n- Prefer explicit errors with useful context over silent fallbacks.\n\n## Host Path Handling\n\n- For every path input, identify whether it belongs to the local host, a remote backend, or the guest before resolving it. Never resolve cloud or guest paths against the SDK client's working directory.\n- Resolve local host paths against their documented base once, before deferred use or persistence. Retained handles and asynchronous operations must reuse that resolved path, including their final writes. Sandbox and SDK configuration-file inputs use the contributing file's directory, including managed SDK settings.\n- Making a path absolute must preserve its symlink policy. Do not substitute filesystem canonicalization or collapse parent components across symlinks without reviewing the behavior change.\n- Preserve existing persisted relative paths and their legacy startup/resource-inheritance behavior. They have no reliable original base unless it was saved: do not reject, migrate, prompt about, or rewrite them during restart. Capture absolute host inputs only for new sandboxes, including new restore/branch children, without modifying their source sandbox.\n- Path changes need regression coverage with different creation and consumption directories, plus missing targets and symlinks where applicable. Change process cwd only inside an isolated test subprocess. Review sandbox mounts, rootfs paths, TLS files, backend storage roots, snapshots, transfers, and generated commands when adding a new path input.\n\n## Backward Compatibility Review\n\nBackward-compatibility detection is a required part of working on this project. Surface potential compatibility breaks before making or continuing the affected change.\n\nThe review's immediate goal is proactive detection and reporting, not automatically implementing compatibility fixes. Do not silently add compatibility layers, migrations, legacy codecs, fallback paths, or downgrade behavior. When a material risk is found, explain the affected releases, components, persisted artifacts, users or workflows, the likely failure mode, and the available options; then wait for human direction as required by the Design Principles above.\n\nSDK-to-`msb` compatibility is required in both directions for future changes:\n\n- Newer SDKs must continue to work with older `msb` binaries for existing supported workflows.\n- Newer `msb` binaries must continue to work with older SDKs for existing supported workflows.\n- Apply this requirement to every language SDK and the complete SDK/runtime interaction, including binary resolution, launch arguments and JSON, inherited descriptors, startup responses, control and agent protocols, and shared persisted state. Compatibility with an already-running agent alone does not establish launch compatibility.\n- A new feature unavailable in an older peer must be detected and produce a clear unsupported-feature or upgrade-required error, or use an explicitly approved fallback. It must not silently lose requested behavior or break unrelated existing functionality.\n- Do not assume matching package versions or bundled binaries satisfy this requirement. Review independently installed runtimes, including those resolved from `MSB_HOME`, and use cross-version tests or historical fixtures for affected boundaries; same-version tests alone are insufficient evidence.\n\nBidirectional SDK/runtime compatibility does not mean freezing the shared catalog at the oldest installed or running version. Newer SDKs and CLIs may apply validated catalog upgrades while older VM runtimes remain running. Do not add a blanket \"stop all sandboxes before upgrading\" gate or preserve an old catalog solely because an older runtime is present. Distinguish an older VM process continuing its database writes from an older SDK/CLI reopening the catalog and running its own schema-admission checks; test and report these separately rather than treating one as evidence for the other.\n\nFor online catalog upgrades, preserve migration serialization, transaction safety, recovery journals, and active maintenance leases. Evaluate the actual SQL and persisted-data contracts used by older runtimes; neither an additive-looking migration nor a version difference alone proves safety or incompatibility. Live tests must exercise an older VM across the upgrade, verify retained execution and data, and cover new-SDK launch, control, restart, and cleanup through older runtimes. If a concrete incompatible migration is found, report that specific conflict for a decision rather than reintroducing a blanket compatibility blocker.\n\nIf a proposed change may violate either direction, flag it before implementation and wait for human direction; this requirement does not authorize silently building adapters or choosing a breaking change. Any exception or change to the supported compatibility horizon requires explicit human direction. The known v0.6.9 ↔ v0.6.10 launch incompatibility is an accepted historical exception and must not be repaired as part of unrelated work. It does not exempt future changes from this review or requirement.\n\nBy explicit user direction on 2026-09-13, the historical SDK/runtime compatibility target for this work is v0.6.x, with v0.6.0 as the floor, against the current implementation candidate in both directions. Releases older than v0.6.0, including all v0.4.x and v0.5.x releases, are excluded from required compatibility. Preserve their historical results as diagnostic evidence. This supersedes the earlier exact-v0.5.0 exclusion. Existing codecs and capability gates are not removed or changed by this scope decision. The known v0.6.9 ↔ v0.6.10 exception remains unchanged; other v0.6.x failures are not waived.\n\nBefore changing an existing cross-version boundary, determine:\n\n- What older component, binary, sandbox, or persisted state may interact with the change.\n- Which side is the old producer or consumer.\n- Whether failure would be a clean refusal, loss of functionality, stranded state, silent misinterpretation, data loss, or possible corruption.\n- Whether the repository has a fixture or test using an actual older artifact or binary, rather than only same-version round trips.\n\nAlways perform this review when a change affects any of these areas:\n\n- Host-to-guest framing, message names, flags, IDs, payload fields, version negotiation, bootstrap, or lifecycle handshakes.\n- Unix sockets, Windows named pipes, path hashing, relay routing, control messages, inherited file descriptors, signals, or process-launch JSON.\n- SQLite schemas, migration history, persisted configuration, downgrade behavior, journals, leases, or runtime recovery state.\n- ext4, EROFS, VMDK, OCI layers, filesystem metadata, device IDs, mount tags, block paths, or other on-disk formats.\n- Snapshots, archives, manifests, canonical serialization, digests, parent identities, filenames, or atomic publication order.\n- Cache keys, materializer ABIs, layer ordering, whiteouts, hardlinks, xattrs, or content-addressed storage.\n- The runtime, agentd, libkrun, firmware, kernel patches, virtio devices, shared-memory layouts, or package-version coupling.\n- Network address derivation, MACs, interface names, DNS aliases, published ports, TCP/UDP behavior, vsock, SSH, or SFTP behavior.\n- Heartbeats, boot errors, metrics, logs, lock files, runtime directories, or other files consumed across process or release boundaries.\n\nCheck each applicable compatibility direction:\n\n1. A new host interacting with an old running sandbox and old agentd.\n2. A new release opening an existing `MSB_HOME` and its database.\n3. A new release reading old disks, snapshots, archives, caches, and filesystem metadata.\n4. An older release encountering state written by the new release, including downgrade refusal behavior.\n5. Exported artifacts moving between releases, platforms, or architectures.\n6. Independently running components from different releases communicating during an upgrade.\n7. A newer SDK launching and operating an older `msb` binary.\n8. An older SDK launching and operating a newer `msb` binary.\n\nTreat stable strings, numeric constants, paths, hashes, serialized field details, ordering guarantees, timing, and error interpretations as compatibility-sensitive even when they are not part of the public API.\n\nFor changes affecting persisted data or cross-version communication, read [COMPATIBILITY.md](COMPATIBILITY.md) before implementation. Follow its contract-version naming, module ownership, migration, and validation guidelines.\n\n## Path Handling\n\n- Every relative path must have an explicit base directory. Never rely on the working directory at the time of eventual use.\n- Resolve CLI paths against the invocation directory, config-file paths against the config file’s directory, and SDK paths against a documented base.\n- Capture that base and resolve host paths before spawning asynchronous work or passing them to another process.\n- Persist absolute host paths when they reference a fixed local resource that must remain the same across restarts.\n- Distinguish host paths, guest paths, volume-relative paths, and resource identifiers. Do not apply host-path normalization to all strings.\n- Making a path absolute, collapsing `..`, and resolving symlinks are different operations. Choose deliberately; they can select different destinations.\n- Use structured path fields internally. Avoid concatenating paths into delimiter-separated strings that become ambiguous with valid filenames.\n- Return explicit errors when resolution fails. Never silently substitute another directory or panic.\n- Enforce filesystem containment during the operation, accounting for symlinks and concurrent changes. String-prefix checks alone are insufficient.\n- Centralize path-resolution rules so CLI, SDK, and background execution cannot drift.\n\nFor path-related changes, test creation in directory A followed by use from directory B, plus relevant symlink, `..`, missing-path, and platform-specific cases. Assert the exact file or directory accessed—not just whether the operation succeeded.\n\n## Rust Layout And Style\n\n- Most Rust crates use `lib/lib.rs` for library code and `bin/main.rs` for binaries. Keep using those paths for new crate entries unless the surrounding crate already does something different.\n- When adding a new library or binary target, declare the path explicitly in `Cargo.toml`:\n\n```toml\n[lib]\npath = \"lib/lib.rs\"\n\n[[bin]]\nname = \"example\"\npath = \"bin/main.rs\"\n```\n\n- Keep crate roots and module roots thin. `lib.rs` and `mod.rs` should declare modules, crate attributes, and exports only. Put implementation in leaf modules such as `sandbox/config.rs`, `policy/types.rs`, or `commands/run.rs`.\n- File order should be:\n  1. Module docs and crate/file attributes, such as `//! ...` and `#![warn(missing_docs)]`.\n  2. `use` imports.\n  3. Sectioned items.\n- Group imports by origin, separated by blank lines: standard library first, external crates second, then `crate::` and `super::` imports.\n- Do not put `use` statements inside sections unless there is a narrow local reason, such as a test module import.\n- Strongly prefer importing types at the top of the file or test module over spelling out full module paths in signatures, patterns, and function bodies. Use an import alias for name collisions; keep qualified paths when they meaningfully clarify an otherwise ambiguous name.\n- Use the exact section delimiter shown below. Do not invent alternate Markdown-style, shorter, or decorative section headers.\n- Include only sections that contain items. Do not add empty sections just to satisfy the full order.\n- Organize Rust files with these section headers, in this order when applicable:\n\n```rust\n//--------------------------------------------------------------------------------------------------\n// Constants\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Types\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Methods\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Trait Implementations\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Functions\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Macros\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Tests\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Re-Exports\n//--------------------------------------------------------------------------------------------------\n```\n\n- Aggregator files that only expose modules and public items may use `Exports` instead of `Re-Exports` when matching existing files.\n- Use qualified section labels only to split large sections into obvious groups, for example `Types: Identifiers`, `Functions: Handlers`, or `Functions: Helpers`.\n- Do not create a qualified section for one or two items unless the surrounding file already uses that pattern.\n- Put constants and statics under `Constants`.\n- Put `struct`, `enum`, `trait`, and `type` definitions under `Types`.\n- Put inherent `impl Type` blocks under `Methods`, directly after the related type definitions when practical.\n- Put `impl Trait for Type` blocks under `Trait Implementations`.\n- Put free functions under `Functions`. If a free function is only used by one public function, place it later in `Functions: Helpers`.\n- Put macros under `Macros`, not near the call site.\n- Put unit tests under `Tests`, usually as `#[cfg(test)] mod tests`. Keep test-only helpers in the same section.\n- Put public re-exports under `Re-Exports`, or `Exports` in root files that use the existing aggregator style.\n- Keep items in dependency order inside a section: public surface first, private helpers later.\n- Keep docs on public types, fields, methods, functions, and modules. This repo uses `#![warn(missing_docs)]` in public crates, so new public items should explain what they are for.\n- Prefer explicit domain types over loosely typed strings, booleans, or tuples when the value crosses an API or subsystem boundary.\n- Separate logical steps with blank lines: setup, early returns, normalization, validation, operations, and final results. Apply the same spacing to test setup, actions, and assertions; do not rely on `cargo fmt` to provide it.\n- Prefer named intermediate values when an expression combines distinct steps, especially fallible lookups, parsing, and validation. Make each failure point easy to identify.\n- Keep iterator chains simple. Prefer a straightforward loop when nested closures or branching make the logic harder to follow; do not add helpers solely to hide that complexity.\n- Keep straightforward logic together. Extract helpers when they clarify a distinct responsibility or remove meaningful duplication, rather than merely shortening a function.\n- Write comments to explain intent, constraints, or non-obvious behavior rather than narrating the code.\n- Name values for what they currently represent. Avoid names that imply an action has already happened when the value describes a requirement or planned action.\n- During refactors, conflict resolution, bug fixes, and feature work, call out any expected behavior, API, or data-format changes and wait for direction when the risk is material.\n- Use `thiserror` or existing local error patterns for typed errors. Include enough context for callers to understand the failing operation.\n- Keep shared library and SDK errors interface-neutral: describe the problem and remedy without CLI flags or command syntax. Add command-specific guidance in the CLI layer.\n- In async code, avoid holding locks across `.await`. Prefer explicit ownership, short critical sections, and existing Tokio patterns in the surrounding module.\n- Keep feature-gated code close to the feature it gates and use existing `#[cfg(feature = \"...\")]` patterns.\n- Do not add examples under `examples/` unless requested or clearly required. Prefer tests and docs for small usage coverage.\n- Run `cargo fmt` before finalizing Rust changes.\n\n## Development Build Notes\n\n- If you build `msb` to run it locally on macOS, make sure the binary is codesigned with `msb-entitlements.plist`; otherwise VM/runtime failures may be caused by missing entitlements instead of your code change.\n- Prefer `just build` or `just build-msb` when producing a runnable local binary. The macOS recipe rebuilds `msb` and runs:\n\n```bash\ncodesign --entitlements msb-entitlements.plist --force -s - build/msb\n```\n\n- If you bypass `just` and call `cargo build` directly, manually codesign the exact `msb` binary you are going to run before testing sandbox startup, protocol, networking, or filesystem behavior.\n\n## Validation\n\nUse focused checks for the files you touched, then broader checks when the change crosses crate, SDK, CLI, or runtime boundaries.\n\nCommon Rust checks:\n\n```bash\ncargo fmt --all -- --check\ncargo clippy --workspace -- -D warnings\ncargo test --workspace\ncargo build -p microsandbox-cli\n```\n\n`agentd` is a workspace member, so the workspace-wide commands above cover it. The musl guest binary that ships in releases is built separately via `just build-agentd`.\n\nPython SDK checks:\n\n```bash\ncd sdk/python\nuv sync --group dev\nuv run maturin develop --release\nuv run pytest\nuv run ruff check .\n```\n\nNode SDK checks:\n\n```bash\ncd sdk/node-ts\nnpm ci\nnpm run build\nnpm test\nnpm run typecheck\n```\n\nGo SDK checks:\n\n```bash\ncd sdk/go\ngo test -count=1 .\ngo test -tags \"smoke microsandbox_ffi_path\" -count=1 -timeout 2m .\n```\n\nIntegration tests may require Linux with KVM or macOS Apple Silicon support. If a needed check cannot run in the current environment, say exactly which command was skipped and why.\n\nThe full local setup and build loop is documented in `DEVELOPMENT.md`. Use `just setup`, `just build`, and `just install` when you need the full local runtime, `agentd`, or `libkrunfw` artifacts.\n\n## Commits\n\n- Use Conventional Commits for commit titles: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `ci`, or `build`.\n- Use a scope when it clarifies the affected area, for example `fix(network): ...` or `docs(sdk): ...`.\n- Keep the subject imperative, lowercase after the colon, no trailing period, and at most 72 characters.\n- Include a commit body for non-trivial changes. Explain what changed and why.\n- Use signed commits: `git commit -S`.\n- Before committing, inspect the actual diff, including new, modified, and deleted files. Do not write a commit message from filenames or previous commit messages alone.\n- If there is nothing to commit, say so rather than creating an empty commit.\n\nExample:\n\n```text\nfix(network): wake smoltcp after accepted host connections\n\nNotify the network loop after accepting a published-port connection so\npending guest traffic can make progress without waiting for another timer.\n```\n\n## Branches And Pull Requests\n\n- You may make local edits while on `main`, but do not commit directly to `main`. Start from the latest `main` when creating a contribution branch.\n- Use short, descriptive, kebab-case branch names. Avoid personal prefixes in shared documentation unless the maintainer asks for one.\n- Before opening a PR, compare against the intended base branch and inspect the actual diff:\n\n```bash\ngit log origin/main..HEAD --oneline\ngit diff origin/main..HEAD --stat\ngit diff origin/main..HEAD --name-status\n```\n\n- PR titles should follow Conventional Commit style and stay under 72 characters.\n- PR descriptions should be plain and accurate:\n  - `## TL;DR`: one or two short sentences.\n  - `## Description`: a flat bullet list of core changes.\n  - `## Test Plan`: concrete commands or observable checks.\n- Do not use emojis in PR titles or descriptions.\n- If a PR description includes an API example, verify every symbol, path, flag, type, field, and signature against the diff before writing it.\n\n## Version And Release Changes\n\n- Do not bump versions, publish packages, create release tags, or modify release automation unless explicitly asked.\n- All released packages share a version. When a version bump is requested, check `Cargo.toml`, `sdk/node-ts/package.json`, `mcp/package.json`, and any other package metadata touched by the release process in `DEVELOPMENT.md`.\n- For release or version PRs, summarize the user-visible changes since the previous version bump and run the relevant dry-run publish checks when practical.\n\n## Documentation And Examples\n\n- For changes under `docs/`, read [the Microsandbox docs skill](.agents/skills/microsandbox-docs/SKILL.md) for page structure, examples, version labels, and validation.\n- Update docs when behavior, configuration, CLI flags, SDK APIs, or examples change.\n- Keep SDK reference introductions to a short purpose statement and a link to the relevant usage guide, then go straight into API entries. Follow the concise structure of the execution and networking reference pages across all SDK languages.\n- Start reference type sections directly with their members when the type heading is sufficient. Avoid redundant “Built by” / “Used by” rows and introductory text that repeats the member descriptions.\n- Do not prepend feature walkthroughs, lifecycle summaries, implementation details, or long examples to reference pages or type sections. Put API-specific details beside the relevant member and broader explanations in the usage guides; avoid duplicating those guides in the reference.\n- In SDK reference pages, wrap usage examples in a collapsed `<Accordion title=\"Example\">`. Keep API signatures and type definitions visible outside the accordion.\n- When removing reference overviews, preserve unique signatures, constraints, result fields, and resource-cleanup requirements beside the relevant member or in the linked usage guide.\n- Omit field lists that only repeat documented builder options. Keep field references for returned data and properties not covered by the builder.\n- Do not use tables in SDK references. Use individual method and property headings with visible signatures and the existing parameter/return blocks. Group returned hash or record fields in the existing field blocks, including types, descriptions, and defaults where needed. Preserve links and existing anchors, and avoid repeating the same description above and inside a return block.\n- In Rust documentation examples, import types at the top of each snippet and use their short names in the body instead of fully qualified paths. Group standard-library imports before external-crate imports.\n- Keep examples realistic and runnable. Do not invent APIs or flags.\n- Prefer editing existing examples over adding new example projects unless the new example is requested or clearly fills a missing user workflow.\n- Documentation should describe current behavior, not future plans, unless the page is explicitly about roadmap work.\n\n## Agent Operating Rules\n\n- Use `rg` or `rg --files` for repository searches.\n- Read the relevant files before editing. Let existing module boundaries guide the change.\n- Make the smallest coherent change that satisfies the request.\n- Avoid destructive git commands such as `git reset --hard` and `git checkout --` unless explicitly requested.\n- Do not edit generated artifacts, lockfiles, or submodule pointers unless the change requires it.\n- If generated files or lockfiles must change, explain why in the final summary.\n- Report what changed, what validation ran, and any checks that were skipped.\n","packages/agent-client/README.md":"# microsandbox agent client\n\nTransport-agnostic clients for speaking the microsandbox agent protocol.\n\nThis layer sits between the protocol package and the high-level SDKs:\n\n```text\nsdk -> agent-client -> protocol-client -> protocol\n```\n\nThe agent package supplies connection handshakes, its existing message codec, and protocol-generation gates. Its public client is a specialization of the shared generic router, which owns IDs, framing, transport lifetime, and request/stream routing. SDK packages own sandbox lifecycle, name resolution, image management, volumes, and metrics.\n\n## Layout\n\n```text\npackages/agent-client/\n├── rust/\n└── typescript/\n```\n\n- `rust/` publishes as `microsandbox-agent-client`.\n- `typescript/` publishes as `@microsandbox/agent-client`.\n\nThe Rust crate has no default transport feature. Local SDK connections opt into `uds` on Unix or `named-pipe` on Windows. An owned `AsyncRead + AsyncWrite` transport can also be supplied, including a caller-authenticated WebSocket adapted to bytes.\n\nThe TypeScript package exposes a browser-safe default entry and keeps Node-only Unix sockets behind `@microsandbox/agent-client/node`; browser/front-end callers use `WebSocketTransport`.\n\n## Message Model\n\nThe public API names protocol boundaries by structure:\n\n- `TypedMessage`: message type plus a native payload object.\n- `EncodedMessage`: message type plus already-CBOR-encoded payload bytes.\n- `RawFrame`: routing fields and an opaque envelope body.\n- `Message` (Rust) / `InboundFrame` (TypeScript): an inspectable envelope retaining its original raw frame.\n- `TransportPacket`: exact transport bytes, including length prefix.\n\nUnchecked packet writes are an explicit escape hatch for relays, tests, and specialized tooling.\n\nRust and TypeScript preserve native, encoded, raw, exact-packet, explicit-ID, and owned split-stream access. See each package README for the API migration and validated examples. Shared TypeScript packages build together through the private npm workspace in `packages`.\n","packages/agent-client/rust/README.md":"# microsandbox-agent-client\n\n`AgentClient` is `Client<AgentProtocol>` from `microsandbox-protocol-client`. The agent package supplies relay setup and generation metadata; the shared engine supplies one reader, writer, allocator, and ownership registry. SDK services such as execution collection and filesystem handles remain in `microsandbox`.\n\n```text\nTypedMessage / EncodedMessage / raw envelope / exact packet\n                           |\n                 Client<AgentProtocol>\n                           |\n          current or legacy relay handshake\n                           |\n                       agent relay\n```\n\n## Native, encoded, and raw messages\n\n```rust,no_run\nuse microsandbox_agent_client::{AgentClient, TypedMessage, EncodedMessage};\nuse microsandbox_protocol::{fs::{FsOp, FsRequest, FsResponse}, message::MessageType};\n\nasync fn inspect(client: &AgentClient) -> Result<(), Box<dyn std::error::Error>> {\n    let request = FsRequest {\n        bulk: None,\n        op: FsOp::Stat { path: \"/etc/os-release\".into(), follow_symlink: true },\n    };\n    let reply = client.request(TypedMessage::new(MessageType::FsRequest, &request)).await?;\n    let response: FsResponse = reply.payload()?;\n    println!(\"{}\", response.ok);\n\n    // The caller's payload bytes are not parsed or normalized by this path.\n    let payload = vec![0xa0];\n    let _reply = client.request(EncodedMessage::new(MessageType::Ping, payload)).await?;\n    Ok(())\n}\n```\n\n`Message.t` is the actual wire string. Unknown names and unknown envelope fields remain inspectable: `Message::raw()` borrows the original frame and `into_raw()` returns it. Ordinary requests surface peer error messages as messages. Optional `Request<AgentProtocol>` implementations add checked unary decoding without narrowing the generic API.\n\n## Owned streams and explicit IDs\n\n```rust,no_run\nuse microsandbox_agent_client::{AgentClient, TypedMessage};\nuse microsandbox_protocol::{exec::{ExecRequest, ExecStdin}, message::MessageType};\n\nasync fn exchange(client: &AgentClient, request: &ExecRequest) -> Result<(), Box<dyn std::error::Error>> {\n    let stream = client.stream(TypedMessage::new(MessageType::ExecRequest, request)).await?;\n    let id = stream.id();\n    let (sender, mut receiver) = stream.into_parts();\n    client.send(id, TypedMessage::new(MessageType::ExecStdin, ExecStdin { data: vec![] })).await?;\n    sender.send(TypedMessage::new(MessageType::ExecStdin, ExecStdin { data: b\"input\".to_vec() })).await?;\n    while let Some(message) = receiver.recv().await? {\n        println!(\"{}\", message.t);\n    }\n    Ok(())\n}\n```\n\nBoth split parts retain the connection and ID lease; sender handles may be cloned and there is one consuming receiver. Terminal receipt disables sends and delivers the terminal frame once. Closing or dropping the receiver disables sends and retains bounded drain state until terminal completion. It sends no process signal or filesystem EOF. Transport loss before a terminal frame is an error, distinct from clean exhaustion.\n\n`request_raw(flags, body)`, `stream_raw(flags, body)`, and `send_raw(id, flags, body)` exchange opaque envelopes without CBOR decoding. `write_unchecked(packet_bytes)` serializes exact caller bytes without allocating an ID or subscribing for replies. `TransportPacket` remains an optional standalone framing helper; `packet.into_bytes()` feeds the exact write API. Standalone packet readers require exclusive transport ownership.\n\n## Setup and metadata\n\nEnable `uds` for native Unix sockets or `named-pipe` for Windows pipes. `connect_stream` accepts an owned `AsyncRead + AsyncWrite + Unpin + Send` transport, including `UdsTransport` and `NamedPipeTransport`. The `stream` feature name remains accepted for existing manifests; generic owned transports are available without additional dependencies.\n\n```rust,no_run\nuse microsandbox_agent_client::AgentClient;\nuse std::time::Duration;\n\nasync fn connect(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {\n    let client = AgentClient::connect_with(path, |o| o.setup_timeout(Duration::from_secs(5))).await?;\n    println!(\"{}\", client.ready().agent_version());\n    println!(\"{}\", client.ready().negotiated_version);\n    println!(\"{} ready bytes\", client.ready().ready_bytes().len());\n    client.close().await;\n    Ok(())\n}\n```\n\nSetup has one deadline across dial and handshake. The current `[id_min,id_max]` relay prologue and the supported pre-0.5 `[id_offset]` prologue retain their existing parsing, ID ranges, and ready payload behavior. Known operations use the smaller host/peer generation as their availability gate. Current connections continue emitting the host's existing envelope generation; legacy connections emit generation one. Source-level API changes do not retire the legacy wire path.\n\n## Migration from the previous active client\n\n| Previous API | Shared-client API |\n| --- | --- |\n| `connect(path)` / `connect_stream(stream)` | Same entry points; owned streams no longer require the `stream` feature |\n| `request(type, &payload)` | `request(TypedMessage::new(type, &payload))` |\n| `stream(type, &payload) -> (id, receiver)` | `stream(TypedMessage::new(type, &payload))`, then `id()` / `into_parts()` |\n| `send(id, type, &payload)` | `send(id, TypedMessage::new(type, &payload))` |\n| `request_raw(flags, body)` | Same call; retains drain state after a nonterminal first reply |\n| `stream_raw(flags, body) -> (id, receiver)` | Owned raw stream, then `id()` / `into_parts()` |\n| `send_raw(id, flags, body)` | Same call, requiring a live owned ID |\n| `connect_with_timeout(path, duration)` | `connect_with(path, |o| o.setup_timeout(duration))` |\n| `connect_stream_with_timeout(stream, duration)` | `connect_stream_with(stream, |o| o.setup_timeout(duration))` |\n| `connect_with_deadline(path, deadline)` | `connect_with(path, |o| o.setup_timeout(deadline.saturating_duration_since(Instant::now())))` |\n| `connect_stream_with_deadline(stream, deadline)` | `connect_stream_with(stream, |o| o.setup_timeout(deadline.saturating_duration_since(Instant::now())))` |\n| `ready()` | `ready().agent` |\n| `ready_bytes()` | `ready().ready_bytes()` |\n| `negotiated_version()` / `supports(type)` | `ready().negotiated_version` / `ready().supports(type)` |\n| `protocol()` / `is_legacy_protocol()` | `ready().wire_format` / `ready().is_legacy_protocol()` |\n| `agent_version()` | `ready().agent_version()` |\n| `ensure_version_compat(type)` | `AgentProtocol::ensure_version_compat_for(type, client.ready().negotiated_version)` |\n| `AgentClient::ensure_version_compat_for(type, generation)` | `AgentProtocol::ensure_version_compat_for(type, generation)` |\n| Unwired `AgentStream<T>` / packet-only `AgentTransport` | Active `AgentStream` / owned byte transports accepted directly by `connect_stream` |\n| Connection close by ownership transfer | Shared `close(&self)`; last client/stream owner also closes the transport |\n\n`ClientError.delivery` distinguishes `NotSent` from `Unknown` after writer admission. Requests are never automatically replayed. Raw stream access, known-ID sends, custom transports, ready bytes, dynamic names, and encoded payloads remain available without adopting SDK domain objects.\n\nDeadline conversions use `tokio::time::Instant`, matching the original absolute-deadline API. Keep the conversion inside the options closure so it uses the remaining duration when setup begins. Known native and encoded sends perform the same availability check automatically; the public `AgentProtocol` helper supports callers retaining only the generation. Unsupported operations now use the shared `ClientError` category rather than the previous agent-specific error fields.\n\nValidation: `cargo test -p microsandbox-agent-client --all-features --locked`. Unix-socket tests require permission to bind temporary local sockets. The migrated historical handshake cases are source-level regression tests; they do not replace live historical runtime/SDK validation.\n\nThe SDK retains an internal optimized client for generation-eight bulk transfers and Unix shared arenas. Its existing bulk APIs remain available; the generic client is the public transport-independent framed API.\n","packages/agent-client/typescript/README.md":"# @microsandbox/agent-client\n\nLow-level TypeScript access to the microsandbox agent protocol. `AgentClient` is `Client<AgentProtocol>` from `@microsandbox/protocol-client`; the agent package supplies relay setup, its existing encoder, and generation gates. The shared engine owns framing, IDs, queues, cancellation, and transport lifetime. Sandbox lifecycle and process/filesystem convenience APIs belong to the SDK.\n\n```text\nAgentProtocol (setup, encoder, gates)\n                 |\n          Client<AgentProtocol>\n            /          \\\n native / encoded     raw / exact packets\n                 |\n       Unix socket / named pipe / WebSocket\n```\n\n## Installation and entry points\n\nInstall with `npm install @microsandbox/agent-client`. Node.js 22 or newer is required for native transports. The root entry is browser-safe. `@microsandbox/agent-client/node` imports Node's `net` module and accepts native endpoint paths verbatim, including Windows named-pipe names.\n\n## Native messages\n\n```ts\nimport { connectUnix } from \"@microsandbox/agent-client/node\";\nimport { typedMessage } from \"@microsandbox/agent-client\";\n\nconst client = await connectUnix(\"/tmp/msb-agent.sock\", { setupTimeoutMs: 10_000 });\ntry {\n  const response = await client.request(typedMessage(\"core.fs.request\", {\n    op: { Stat: { path: \"/etc/os-release\", follow_symlink: true } },\n  }), { requestTimeoutMs: 5_000 });\n  console.log(response.type, response.decodePayload());\n  console.log(client.ready.agentVersion, client.ready.negotiatedVersion);\n} finally {\n  await client.close();\n}\n```\n\n`typedMessage(name, value)` asks the agent codec to encode a native payload. `encodedMessage(name, bytes)` supplies only the application payload; it does not supply an envelope or framed packet. Both paths apply known message gates and flags. Names are open strings, so extensions remain accessible without editing an enum. These methods do not validate an application schema. `requestTyped()` accepts an optional checked request with its own result decoder and requires a terminal response.\n\nThe current TypeScript agent encoder continues to emit generation-five envelopes, including its existing CBOR map and byte-string representations. The ready generation controls feature gates. The supported pre-0.5 relay prologue selects generation-one envelopes and rejects unavailable filesystem/TCP operations before sending. The captured ready frame and unknown fields remain available through `client.ready.frame` and `client.ready.readyBytes`.\n\n## Browser streams\n\n```ts\nimport { AgentClient, WebSocketConnector, typedMessage } from \"@microsandbox/agent-client\";\n\nconst client = await AgentClient.connectConnector(\n  new WebSocketConnector(\"wss://relay.example.com/agent\"),\n  { setupTimeoutMs: 10_000 },\n);\ntry {\n  const stream = await client.openStream(typedMessage(\"core.exec.request\", {\n    cmd: \"sh\", args: [\"-lc\", \"echo hello\"],\n  }));\n  try {\n    for await (const frame of stream) {\n      console.log(frame.type, frame.payload);\n    }\n  } finally {\n    stream.close();\n  }\n} finally {\n  await client.close();\n}\n```\n\nThe relay must forward the agent's binary byte stream, including its prologue. `WebSocketTransport` also wraps a caller-constructed socket for custom authentication. Its incoming queue defaults to 8 MiB and 4096 messages; overflow closes the connection because browser WebSockets cannot pause incoming messages. Outgoing writes wait for the browser's send queue to drain. The connector and handshake share one setup deadline.\n\n## Raw and exact access\n\n```ts\nimport { type AgentClient, TransportPacket } from \"@microsandbox/agent-client\";\n\nasync function exchangeOpaque(client: AgentClient, envelope: Uint8Array) {\n  const stream = await client.openStreamRaw(0, envelope);\n  const { sender, receiver } = stream.split();\n  try {\n    await sender.send(0, envelope);\n    await client.sendRaw(sender.id, 0, envelope);\n    return await receiver.next({ requestTimeoutMs: 5_000 });\n  } finally {\n    receiver.close();\n    sender.close();\n  }\n}\n\nasync function forwardExactPacket(client: AgentClient, bytes: Uint8Array) {\n  await client.writeUnchecked(TransportPacket.fromBytes(bytes));\n}\n```\n\nRaw APIs leave envelope and payload bytes opaque. `requestRaw(flags, body)` returns the first raw reply. `openStreamRaw()` receives through the terminal reply. `sendRaw(id, flags, body)` requires an active ID owned by this connection. A split sender retains its original lease and cannot regain permission if the numeric ID is reused. `writeUnchecked(bytes)` can write even deliberately malformed transport bytes; `TransportPacket.fromBytes()` optionally validates a single packet first.\n\nNative replies retain the complete original raw frame as `frame.raw`, including unknown envelope fields. `core.error` is an ordinary terminal agent response; interpreting it belongs to the caller or a checked request decoder.\n\n## Ownership and failures\n\nStreams own their send and receive halves. `split()` transfers those halves and invalidates the original stream's send/receive methods. Closing a receiver or leaving async iteration stops local delivery; it sends no remote cancel, signal, or EOF. An abandoned ID remains draining until its terminal frame arrives. A timed-out `next()` does not consume the next arriving frame.\n\n`client.clone()` shares the connection. `client.close()` closes every shared handle and wakes pending operations. Explicitly close clients and streams: JavaScript garbage collection only offers best-effort cleanup. A request deadline covers writer admission and the local response wait; it does not cancel remote execution. `ClientError.delivery` is `not_sent` before admission and `unknown` afterward. Nothing automatically retries an admitted operation.\n\n## Migration from the previous package API\n\n| Previous surface | Shared-engine surface |\n| --- | --- |\n| Independent `AgentClient` class/router | `AgentClient = Client<AgentProtocol>` plus convenience constructors |\n| `handshakeTimeoutMs` | `setupTimeoutMs`, covering dial and handshake together |\n| `client.negotiatedVersion()` | `client.ready.negotiatedVersion` |\n| Closed `MessageType` parameter | Open string names; `MessageType` still lists known names |\n| Packet-oriented custom `AgentTransport` | Ordered `read(maxBytes)`, `write(bytes)`, `close()` byte transport |\n| `InboundFrame.fromRawFrame(frame)` | `new AgentEnvelopeCodec().decode(frame)` |\n| `new AgentStream(...)` | Obtain owned streams from `client.openStream()` or `openStreamRaw()` |\n| Request, encoded payload, stream, exact packet methods | Retained on the generic client, with additional raw and split interfaces |\n\nThese are public source API changes. Existing agent wire behavior is preserved; source and byte tests do not replace the required historical runtime/SDK launch tests.\n\n## Development\n\nFrom the repository's `packages` directory, run `npm ci`, `npm run build`, `npm run typecheck`, and `npm test`. The private workspace links the shared package locally; published dependency metadata uses the release version. The README's TypeScript examples are checked with the public package declarations.\n","packages/control-client/rust/README.md":"# Rust control client\n\n`ControlConnection` discovers whether an existing control endpoint supports framed CBOR or legacy JSON. `ControlClient` is the explicitly framed `Client<ControlProtocol>` entry point, and `JsonControlClient` is an explicit legacy unary adapter. Their checked helpers share the same operation records.\n\n```text\nControlConnection -- JSON capabilities --+-- advertised CBOR: fresh stream + hello\n                                        |                      |\n                                        |               ControlClient\n                                        |\n                                        +-- legacy: JsonControlClient\n```\n\nAutomatic discovery has one deadline across the probe, redial, and hello. It selects CBOR only after a valid affirmative capability response. Malformed replies, arbitrary peer errors, empty EOF, and failed framed setup are errors. There is no error-string downgrade or mutation replay. `ControlClient` sends its hello directly when callers already know the peer supports framed control.\n\n```rust,no_run\nuse microsandbox_control_client::{\n    ControlConnection, ControlMessageType, ControlReply, Empty, GetMemoryState, TypedMessage,\n};\n\nasync fn inspect(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {\n    let connection = ControlConnection::connect(path).await?;\n    let reply = connection.request(TypedMessage::new(ControlMessageType::MemoryState, Empty {})).await?;\n    match reply {\n        ControlReply::Framed(message) => println!(\"{}\", message.t),\n        ControlReply::Json(reply) => println!(\"{} original JSON bytes\", reply.raw().len()),\n    }\n    println!(\"{} MiB\", connection.request_typed(&GetMemoryState).await?.target_mib);\n    connection.close().await;\n    Ok(())\n}\n```\n\nIn framed mode, clones reuse one connection. Automatic JSON mode rediscovers before each fresh exchange when its connector cannot prove runtime identity. An owner implementing `VerifiedControlConnector` verifies the connected peer and the runtime's OS birth token on every dial, then rechecks the active run before each operation; `connect_verified_connector` can reuse that owner's JSON selection. A path or PID alone is insufficient. A detected change invalidates the handle before sending instead of silently retargeting the request. The runtime owner, such as an SDK backend, supplies that identity implementation; the protocol package does not claim to verify it itself.\n\n`JsonControlClient::new(path)` and `from_connector(...)` are inert constructors for callers explicitly choosing legacy mode. Its `request` translates only known native requests and returns the actual `JsonReply`, including `ok:false` replies. Checked helpers report `LegacyRemote` with the original reply and unknown batch progress. JSON numbers retain their original tokens; checked memory fields use `u64` without a floating-point intermediate. `connection.framed()` fails locally in JSON mode, and encoded-payload input is rejected there before dialing. Raw streams and exact packets remain available on the explicitly framed client.\n\n`connection.capabilities()` borrows the released generation-1 discovery projection without network I/O, while `connection.runtime_capabilities()` exposes the complete discovered facility set. `GetCapabilities` remains available for a fresh generation-1-compatible observation and `GetRuntimeCapabilities` requests the complete record on generation 2. `closed().await` observes shared closure, including an idle framed disconnect; a successful JSON exchange ending in EOF does not close the session.\n\nGeneration 1 remains the released capabilities, memory, CPU, and secrets surface. Generation 2 adds `CreateCheckpoint`, `CreateDiskCheckpoint`, `CreateBranch`, `PauseRuntime`, `ResumeRuntime`, `GetPauseState`, `GrowRootDisk`, and `CompactDisks`. `ControlConnection` talking to a framed generation-1 runtime selects the historical JSON representation for these checked operations before sending anything; it never retries a mutation after a framed failure. The explicit `ControlClient` instead returns a not-sent unsupported-operation error. Linux descriptor-backed branch creation remains an SDK-level JSON/`SCM_RIGHTS` exception because ordinary framed messages cannot transfer file descriptors.\n\nEnable `uds` for Unix endpoint connections or `named-pipe` for Windows endpoint connections. Caller-owned transports and connectors can be used without either native feature. Endpoint paths are accepted verbatim.\n\n```rust,no_run\nuse microsandbox_control_client::{\n    ControlClient, GetMemoryState, SetMemoryTarget, size::SizeExt,\n};\n\nasync fn resize(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {\n    let client = ControlClient::connect(path).await?;\n    let before = client.request_typed(&GetMemoryState).await?;\n    let accepted = client.request_typed(&SetMemoryTarget::new(2048.mib())).await?;\n    println!(\"{} -> {} MiB\", before.target_mib, accepted.target_mib);\n    client.close().await;\n    Ok(())\n}\n```\n\nMemory wire records retain `u64` values. The existing size helpers retain their conversion rules; direct `SetMemoryTarget { total_mib }` construction provides full-width wire input. Target responses report acceptance and current observations, not guest convergence. Ordered secret batches preserve partial completion and the index of the first failed entry.\n\nThe client keeps `request`, `stream`, `request_raw`, `stream_raw`, explicit-ID `send`/`send_raw`, exact `write_unchecked`, owned `into_parts`, and checked `request_typed` operations. Generic requests return peer error frames as messages; checked helpers interpret them and retain the original response. No sandbox, execution, filesystem, or convergence service is required to use any of these paths.\n\n```rust,no_run\nuse microsandbox_control_client::{ControlClient, EncodedMessage};\n\nasync fn inspect(client: &ControlClient, payload: Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {\n    let response = client.request(EncodedMessage::new(\"extension.inspect\", payload)).await?;\n    println!(\"{}: {} payload bytes\", response.t, response.p.len());\n    Ok(())\n}\n```\n\nSetup defaults to one ten-second deadline and requests to a thirty-second local wait. Request expiry does not cancel remote work or trigger replay. `ClientError` retains `NotSent` versus `Unknown` delivery. Clones and owned streams share a connection; `close` closes it for all owners.\n\nRun `cargo test -p microsandbox-control-client --all-features --locked`. For live tests, set `MSB_CONTROL_TEST_SOCKET` and `MSB_CONTROL_TEST_MODE=cbor` or `json`. The explicitly framed test requires CBOR; run only `live_automatic_discovery_and_explicit_json -- --ignored --exact` against a JSON-only runtime. Use bounded disposable fixtures (512 MiB and two CPUs), and run live tests serially. The automatic test changes targets and restores them through explicit JSON. Optional `MSB_CONTROL_TEST_SECRET` names a dummy fixture initially set to `before` and allowed for `example.invalid`; the test exercises legacy partial-failure reporting and restores that fixture. Skipped live tests are not runtime or platform validation.\n","packages/control-client/typescript/README.md":"# TypeScript control client\n\n`ControlClient` is `Client<ControlProtocol>` over the shared framed engine. It sends a bounded `control.hello` directly, negotiates generation and limits, and exposes the original welcome frame. Use it when the peer is known to support framed control. It does not probe JSON or downgrade after failure.\n\nThe root entry is browser-safe and accepts a caller-owned byte transport or reconnectable connector. The `/node` entry accepts an existing Unix socket or Windows named-pipe path without renaming it.\n\n```ts\nimport { GetMemoryState, MiB, SetMemoryTarget, typedMessage } from \"@microsandbox/control-client\";\nimport { connectFramedControl } from \"@microsandbox/control-client/node\";\n\nasync function inspectAndResize(path: string) {\n  const client = await connectFramedControl(path, { setupTimeoutMs: 10_000 });\n  try {\n    const response = await client.request(typedMessage(\"control.memory.state\", {}));\n    console.log(response.type, response.raw.body);\n    const before = await client.requestTyped(new GetMemoryState());\n    const accepted = await client.requestTyped(new SetMemoryTarget(MiB(2048)));\n    console.log(before.target_mib, accepted.target_mib); // bigint, even for small values\n  } finally {\n    await client.close();\n  }\n}\n```\n\nGeneration-1 checked helpers cover capabilities, memory state/target, CPU state/target, and ordered secret updates. Generation 2 adds `GetRuntimeCapabilities`, `CreateCheckpoint`, `CreateDiskCheckpoint`, `CreateBranch`, `PauseRuntime`, `ResumeRuntime`, `GetPauseState`, `GrowRootDisk`, and `CompactDisks`. They validate replies and retain the actual response on `ControlClientError`. Generic message requests return peer error frames directly. A target reply reports acceptance and observation; it does not establish guest convergence. Secret batches stop at the first failure and retain earlier completed changes.\n\nUse `connectControl` for automatic discovery on the same endpoint. It sends the existing read-only JSON capabilities request, closes that exchange, and opens a fresh framed connection only after an affirmative CBOR advertisement. Setup has one total deadline across both connections. Malformed discovery, timeout, EOF without a reply, or a failed welcome never trigger fallback or replay.\n\n```text\nconnectControl(path)\n    |\n    +-- JSON capabilities --> valid legacy reply --> fresh JSON exchange per operation\n    |\n    +-- JSON capabilities --> advertises CBOR -----> fresh hello/welcome --> shared framed client\n```\n\n```ts\nimport { GetMemoryState, MiB, SetMemoryTarget, typedMessage } from \"@microsandbox/control-client\";\nimport { connectControl } from \"@microsandbox/control-client/node\";\n\nasync function compatibleResize(path: string) {\n  const connection = await connectControl(path);\n  try {\n    const reply = await connection.request(typedMessage(\"control.memory.state\", {}));\n    if (reply.kind === \"cbor\") console.log(reply.frame.raw.body);\n    else console.log(reply.reply.raw, reply.reply.value.get(\"memory\"));\n    const before = await connection.requestTyped(new GetMemoryState());\n    const accepted = await connection.requestTyped(new SetMemoryTarget(MiB(2048)));\n    return { before, accepted };\n  } finally {\n    await connection.close();\n  }\n}\n```\n\n`ControlConnection.connectConnector` accepts a repeatable caller connector from the browser-safe root. The connection exposes the read-only generation-1 `capabilities` projection, the complete `runtimeCapabilities` discovery snapshot, `mode`, `clone()`, `isClosed()`, `close()`, `request()`, and `requestTyped()`. `framed()` returns the complete generic surface in CBOR mode and fails locally with `unsupported_mode` in JSON mode. Encoded messages likewise fail locally in JSON mode; the adapter translates only known, validated native operations.\n\nWhen a framed peer selects generation 1, `ControlConnection.requestTyped()` sends generation-2 checked operations through their historical JSON form before any mutation is admitted. An unverified connector rediscovers the endpoint format first. This path is never a retry after a CBOR failure. The explicit `ControlClient` rejects those operations locally against a generation-1 welcome. Linux descriptor-backed branch creation remains outside ordinary CBOR because it requires `SCM_RIGHTS`; generation 2 covers direct local branching without descriptor transfer.\n\n`JsonReply` contains the original response line, including its delimiter and whitespace, and a lossless `Map` of fields. `JsonNumber.token` retains each original numeric token; checked memory observations become `bigint` without passing through a JavaScript number. Ordinary requests return `ok:false` replies. Checked helpers raise `legacy_remote` with the original reply and unknown batch progress. Neither arbitrary diagnostic strings nor legacy errors are converted into structured CBOR errors.\n\nFor explicit legacy access, construction is inert and performs no discovery:\n\n```ts\nimport { GetCpuState } from \"@microsandbox/control-client\";\nimport { jsonControl } from \"@microsandbox/control-client/node\";\n\nasync function inspectLegacy(path: string) {\n  const client = jsonControl(path);\n  try {\n    return await client.requestTyped(new GetCpuState());\n  } finally {\n    await client.close();\n  }\n}\n```\n\nThe equivalent root API is `JsonControlClient.fromConnector(connector, options?)`. Ordinary legacy request and response lines keep their existing size contract; only automatic discovery replies have a 64 KiB limit. Preparing a secret batch snapshots its entries without imposing the framed 4 MiB ceiling on JSON. JavaScript strings remain subject to garbage collection; explicit buffer clearing is not a guarantee that all copies have been erased.\n\nA standalone automatic JSON connection rediscovers before each fresh operation because an endpoint path cannot establish process identity. `ControlConnection.connectVerifiedConnector` permits discovery reuse when the caller supplies a `VerifiedControlConnector`: `connect` must verify every connected peer against the saved OS process birth identity, and `verifySession` must check the active run and process before each operation. Both receive a deadline and cancellation context. Runtime replacement raises `runtime_changed` before mutation admission and closes the shared handle. This extension point does not implement an SDK database or OS identity verifier by itself.\n\n`MiB`, `GiB`, `KiB`, `TiB`, and `Mebibytes` are exported from `@microsandbox/types/size`. Their arithmetic matches the SDK helpers. `SetMemoryTarget` validates integer precision and range before converting to the wire quantity; it also accepts `bigint` for full-width values.\n\nAll generic operations remain available:\n\n| Need | Method |\n|---|---|\n| Native or encoded payload | `request(typedMessage(...))`, `request(encodedMessage(...))` |\n| Exact opaque envelope | `requestRaw(flags, body)` |\n| Message or raw subscription | `openStream(...)`, `openStreamRaw(...)` |\n| Owned sender and receiver | `stream.split()` |\n| Follow-up using an owned ID | `sendOnStream(id, ...)`, `sendRaw(id, ...)` |\n| Exact packet without a subscription | `writeUnchecked(bytes)` |\n| Optional checked unary operation | `requestTyped(request)` |\n| Shared connection ownership | `clone()`, `close()` |\n\n```ts\nimport { ControlClient, encodedMessage, type ByteTransport } from \"@microsandbox/control-client\";\n\nasync function inspectExtension(transport: ByteTransport, payload: Uint8Array) {\n  const client = await ControlClient.connectTransport(transport);\n  try {\n    const reply = await client.request(encodedMessage(\"extension.inspect\", payload));\n    return { name: reply.type, payload: reply.payload, original: reply.raw };\n  } finally {\n    await client.close();\n  }\n}\n```\n\nThe default control request deadline is 30 seconds. Expiry abandons the local wait; it does not cancel remote work or retry it. `ClientError.delivery` distinguishes requests that were not admitted from unknown outcomes. Complete idle connections do not expire automatically. Explicit close closes every clone; JavaScript finalization is only best-effort cleanup.\n\nFrom `packages`, run `npm run build`, `npm run typecheck`, and `npm test`. Live validation is deliberately opt-in: set `MSB_CONTROL_TEST_SOCKET` to a disposable framed runtime's socket and run `npm test -w @microsandbox/control-client`. Add `MSB_CONTROL_TEST_MODE=cbor` to run automatic discovery as well. The package serializes test files because live files mutate and restore the same VM's targets. A skipped live test is not platform or runtime compatibility evidence.\n\nFor a historical JSON-only runtime, set `MSB_CONTROL_TEST_MODE=json` and run only the compatibility live file with `npm exec -w @microsandbox/control-client -- vitest run tests/compatibility-live.test.ts`. It checks automatic and explicit JSON access, concurrent reads, full-width result types, and accepted memory/CPU targets, then restores the original targets. These running-runtime tests do not establish SDK launch compatibility.\n\nThe additional secret test requires `MSB_CONTROL_TEST_SECRET` naming a dummy fixture initially set to `before` and allowed for `example.invalid`. It exercises partial completion, malformed batches, and legacy JSON failures, then restores those fixture values.\n","packages/microsandbox-types/README.md":"# microsandbox types\n\nShared task and wire contract types for microsandbox.\n\nThese packages define the backend-neutral shapes that describe what a sandbox should be: its rootfs source, resources, mounts, patches, network, lifecycle, and the cloud HTTP request/response bodies that carry those specs over the wire. Everything that has to agree on a sandbox's shape (the Rust SDK, the CLI, the cloud API, and the TypeScript front end) depends on these contracts instead of redefining them.\n\nThis is a contract layer, not a runtime. It does not create, start, or talk to sandboxes. It only models the data those operations exchange.\n\n## Layout\n\n```text\npackages/microsandbox-types/\n├── rust/\n└── typescript/\n```\n\n- `rust/` publishes as `microsandbox-types` (crate name `microsandbox_types`). It is the source of truth for every shared type.\n- `typescript/` publishes as `@microsandbox/types`. Its `src/cloud.ts` (entry) and `src/domain.ts` are generated from the Rust types, never hand-edited; only the cloud contract and the domain types it references are emitted.\n\n## Source Of Truth\n\nThe Rust crate owns the definitions. The TypeScript bindings are derived from them with [`ts-rs`](https://github.com/Aleph-Alpha/ts-rs) behind the crate's `ts` feature, so the two stay byte-for-byte aligned.\n\n```text\nrust/lib/*.rs  ──(ts-rs)──▶  typescript/src/{cloud,domain}.ts\n```\n\nTo regenerate the bindings after changing a Rust type:\n\n```bash\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate\n```\n\nCI runs the same generator with `--check` and fails when the checked-in bindings drift:\n\n```bash\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate -- --check\n```\n\n## What Lives Here\n\n- Sandbox specs: `SandboxSpec`, `SandboxResources`, `SandboxRuntimeOptions`, rootfs sources, mounts, patches, init, lifecycle policy.\n- Networking intent: `NetworkSpec`, published ports, protocols.\n- Volumes and snapshot manifests: `VolumeSpec`, `SnapshotSpec`, and their kinds.\n- Exec and logging: `Rlimit`, `RlimitResource`, `LogSource`, `SandboxLogLevel`.\n- Cloud wire contracts: the source-tagged `CloudCreateSandboxRequest` union,\n  `CloudSandbox`, snapshot resources and operations, and\n  paginated/message/error bodies.\n\nCloud snapshot contracts distinguish the durable resource from the asynchronous\ncapture operation:\n\n- `CloudCreateSnapshotRequest` is discriminated by `kind`. Its current `disk`\n  variant captures the writable disk, and the enum can gain other snapshot\n  kinds without changing the envelope shape.\n- `CloudSnapshot` uses the same kind discriminator and includes its canonical\n  manifest, byte size, labels, and `CloudSnapshotLocation`.\n- `CloudSnapshotLocation` is either a managed artifact ID or a host-volume\n  path. The same type is used when restoring a sandbox so location semantics\n  are not duplicated.\n- `CloudSnapshotOperation` carries the requested kind and tracks capture\n  through `queued`, `in_progress`, `succeeded`, or `failed`; `result` is\n  populated on success.\n- Validation helpers: sandbox-name and hostname rules shared across SDK, CLI, and cloud.\n\nBackend-private materialized state (registry credentials, local cache paths, DB rows, resolved manifest digests, process handles) deliberately stays out of these packages. See each language's README for details.\n\n## Cloud Request Compatibility\n\nCloud request objects ignore unknown fields, including fields inside network and runtime options. This allows newer SDKs to send additive options before the receiving Cloud release understands them. Unknown settings have no effect until that server supports them; request success does not confirm that every supplied setting was applied.\n\nMissing fields retain their documented defaults. Required fields, recognized field types and values, and enum variants still undergo validation. Ignoring unknown object keys does not make new enum variants, renamed fields, or changed field types compatible.\n\nThis policy applies to public Cloud wire types, not host or operator configuration. SDK checks for runtime-only options remain separate from the server's treatment of future Cloud fields.\n","packages/microsandbox-types/rust/README.md":"# microsandbox-types\n\nShared task and wire contract types for microsandbox.\n\nThis crate is the source of truth for the backend-neutral shapes that describe a sandbox and the cloud HTTP bodies that carry them. The Rust SDK, the CLI, and the cloud backend all import these types so they agree on one definition instead of duplicating wire shapes. The generated `@microsandbox/types` TypeScript package is derived from this crate.\n\nIt is a leaf dependency by design. It pulls in `serde`, `serde_json`, `chrono`, `sha2`, and `thiserror`, and nothing from the local VM machinery (no runtime, image, network, or database crates). That keeps it cheap enough for front-end generation, cloud API models, and SDK wrappers to all depend on.\n\n## What This Crate Owns\n\nThe crate models durable user and wire intent: what the user wants to exist, not how a backend fulfills it.\n\n- **Sandbox spec** (`domain` module): `SandboxSpec`, `SandboxResources`, `SandboxRuntimeOptions`, `EnvVar`, `SandboxPolicy`.\n- **Rootfs sources**: `RootfsSource` (bind, OCI, disk image), `OciRootfsSource`, `DiskImageFormat`, `PullPolicy`.\n- **Mounts and patches**: `VolumeMount`, `MountOptions`, `StatVirtualization`, `HostPermissions`, `Patch`, `SecurityProfile`.\n- **Volumes and snapshots**: `VolumeSpec`, `VolumeKind`, `NamedVolumeCreate`, `NamedVolumeMode`, `SnapshotSpec`, and the canonical `SnapshotManifest` schema.\n- **Networking**: `NetworkSpec`, `PublishedPortSpec`, `PortProtocol`.\n- **Exec and logs**: `Rlimit`, `RlimitResource`, `SandboxLogLevel`, `LogSource`, `HandoffInit`.\n- **Cloud wire contracts** (`cloud` module): sandbox, snapshot, pagination, message, and error request/response types.\n- **Validation** (`validation` module): `validate_sandbox_name`, `validate_hostname`, `hostname_from_sandbox_name`, and the `MAX_SANDBOX_NAME_BYTES` / `MAX_HOSTNAME_BYTES` limits.\n\nBackend-private materialized state stays out: registry credentials, local CA paths, replace flags, pull-discovered manifest digests, snapshot upper paths, process handles, and DB rows belong to the SDK and backends, not the contract.\n\n## Usage\n\n```toml\n[dependencies]\nmicrosandbox-types = \"0.7.0\"\n```\n\n```rust\nuse microsandbox_types::{RootfsSource, SandboxResources, SandboxSpec};\n\nlet spec = SandboxSpec {\n    name: \"worker\".into(),\n    image: RootfsSource::oci(\"python\"),\n    resources: SandboxResources { cpus: 2, memory_mib: 1024 },\n    ..Default::default()\n};\n```\n\nThe Rust SDK re-exports the contract types it accepts, so most SDK users get these through `microsandbox::*` and do not depend on this crate directly.\n\n## Serialization Notes\n\n- `SandboxSpec`, `SandboxResources`, `SandboxRuntimeOptions`, `NetworkSpec`, and `MountOptions` use `#[serde(default)]`, so partial JSON fills missing fields from static defaults.\n- Lowercase-on-the-wire enums (`StatVirtualization`, `HostPermissions`, `SecurityProfile`, `SandboxLogLevel`, `LogSource`, `PortProtocol`) match the CLI grammar and the TypeScript string unions.\n- `VolumeMount` has hand-written `Serialize`/`Deserialize`. It tags variants with a `type` field and accepts a legacy top-level `readonly` flag, folding it into `MountOptions` on read.\n- Static defaults only. Nothing here reads process-global or profile config; the SDK and backends apply environment defaults before execution.\n\n## TypeScript Generation\n\nThe `ts` feature derives `ts_rs::TS` on exported wire types and builds the `microsandbox-types-generate` binary, which writes `domain.ts`, `snapshot.ts`, and `cloud.ts` beneath `../typescript/src`.\n\n```bash\n# Regenerate the checked-in bindings.\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate\n\n# Verify they are current (used in CI; exits non-zero when stale).\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate -- --check\n```\n\nA unit test (`checked_in_bindings_match_generated_output`) also fails when any generated TypeScript file drifts, so `cargo test --features ts` catches stale bindings too.\n\n## Testing\n\n```bash\ncargo test -p microsandbox-types\ncargo test -p microsandbox-types --features ts\n```\n","packages/microsandbox-types/typescript/README.md":"# @microsandbox/types\n\nShared task and wire contract types for microsandbox, as TypeScript.\n\nThis package is the type-only mirror of the cloud contract in the `microsandbox-types` Rust crate. It gives TypeScript consumers (the cloud front end and anything talking to the cloud API) the exact cloud wire shapes microsandbox uses, so a `CloudSandboxSpec` means the same thing on both sides of the wire. Only the domain types the cloud contract references are generated; the broader SDK/domain surface is not.\n\nIt contains no runtime code. Every export is a type; importing it adds nothing to your bundle.\n\n## Generated, Not Hand-Written\n\nThree files are generated from the Rust crate with [`ts-rs`](https://github.com/Aleph-Alpha/ts-rs), each carrying a `// @generated` header. Do not edit them:\n\n- `src/cloud.ts` — the package entry: the cloud wire types, which import and re-export their domain and snapshot dependencies.\n- `src/domain.ts` — only the domain types the cloud twins transitively reference (`EnvVar`, `Rlimit`, `NetworkPolicy`, …).\n- `src/snapshot.ts` — the canonical snapshot manifest schema embedded by cloud snapshot resources.\n\nTo change a shape, edit the Rust type and regenerate from the repo root:\n\n```bash\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate\n```\n\n## Install\n\n```bash\nnpm install @microsandbox/types\n```\n\n## Usage\n\nImport the shapes you need with `import type`:\n\n```ts\nimport type {\n  CloudCreateSandboxRequest,\n  CloudSnapshot,\n  CloudSnapshotOperation,\n} from \"@microsandbox/types\";\n\nfunction createSandbox(request: CloudCreateSandboxRequest) {\n  // POST the source-tagged request to the cloud API\n}\n```\n\n`CloudCreateSnapshotRequest` and `CloudSnapshot` are unions discriminated by\n`kind`; the current public variant is `\"disk\"`. `CloudSnapshotOperation` carries\nthat same kind and exposes only the public statuses `queued`, `in_progress`,\n`succeeded`, and `failed`. Both snapshot creation results and disk-snapshot\nrestore requests use `CloudSnapshotLocation` to distinguish a managed artifact\nID from a host-volume path.\n\n## Generated Shape Notes\n\nThe bindings follow `ts-rs` conventions, which mirror the Rust serde representation:\n\n- Cloud enums are internally tagged with a `type` field: `CloudRootfsSource` is `{ type: \"bind\"; … } | { type: \"oci\"; reference: string } | { type: \"disk_image\"; … }`, and `CloudVolumeMount` / `CloudHostPattern` / `CloudViolationAction` follow the same shape.\n- `CloudCreateSandboxRequest` is a flat union discriminated by `source: \"oci\" | \"bind\" | \"disk_image\" | \"disk_snapshot\"`. Common sandbox fields stay flat on every variant; source-specific fields such as `reference`, `path`, and `disk_snapshot_ref` appear only where they apply. Servers also accept the legacy `image` request shape without `source` during migration.\n- `CloudCreateSnapshotRequest` and `CloudSnapshot` are flat unions discriminated by the current `kind: \"disk\"`; common snapshot fields stay flat beside the discriminator.\n- Lowercase domain enums like `StatVirtualization` are string-literal unions (`\"strict\" | \"relaxed\" | \"off\"`).\n- Optional Rust fields are `T | null`; fields skipped when absent are `?:` optional.\n- Referenced domain and snapshot types live in `domain.ts` and `snapshot.ts` and are re-exported from the package entry, so a single import from `@microsandbox/types` sees the whole cloud contract.\n\n## Build And Typecheck\n\n```bash\nnpm run build       # tsc -> dist/\nnpm run typecheck   # tsc --noEmit\n```\n","packages/protocol-client/rust/README.md":"# Generic framed client\n\n`Client<P>` owns one byte connection, its correlation IDs, and all routed subscriptions. `P: Protocol` establishes the transport and selects message metadata and an `EnvelopeCodec`. Protocol consumers can implement both traits outside this crate. `tests/engine.rs` exercises external protocol setup and routing, while `tests/external_codec.rs` supplies an external connector and a non-CBOR envelope codec across native, encoded, raw, checked, stream, explicit-ID, and exact-packet calls. Rust native payload serialization remains CBOR; the custom codec controls its enclosing envelope, and encoded/raw paths retain caller-supplied bytes.\n\n```text\nnative payload ---- TypedMessage ---+\nencoded payload --- EncodedMessage -+--> codec --> shared writer\nraw envelope -----------------------+-----------> shared writer\nexact packet -----------------------------------> shared writer\n\nshared reader --> ID routing --> RawStream --> Stream (decode on recv)\n```\n\n`request` returns the first response. If it is not terminal, the router retains the ID and discards later replies until terminal completion. `request_typed` additionally requires a terminal response and invokes a borrowed `Request<P>` implementation's checked decoder. Application errors remain that implementation's responsibility; the generic engine does not equate a terminal response with success.\n\n`stream` and `stream_raw` return owned subscriptions. `into_parts()` moves them into a cloneable sender and one receiver. Both retain the connection. Dropping or closing the receiver disables sends and drains any admitted operation until terminal completion; it does not send a process signal, file EOF, TCP close, or cancellation packet. Old sender handles cannot gain permission when an ID is reused.\n\n`send` and `send_raw` support explicit IDs owned by the connection. `write_unchecked` serializes caller-owned packet bytes without creating an ID or response subscription. Native and encoded-payload paths apply protocol availability gates; raw paths preserve opaque bytes. A decoded `Message` retains its original `RawFrame`, including unknown envelope fields.\n\nConnections are shared handles. Explicit `close(&self)` closes every handle and wakes waiters. `closed().await` observes closure, including idle peer disconnection, without polling; canceling that wait leaves the connection alone. Last-owner drop closes the transport; the reader and writer do not keep the owner alive. A stream remains an owner after a temporary client handle is dropped.\n\nQueues have item and combined byte limits. A retained slow receiver can backpressure the whole connection; fair per-stream flow control is not provided by the wire protocol. Dropping that receiver releases its queued frames and wakes blocked routing. Active and draining IDs both consume in-flight capacity.\n\n`ClientError.delivery` distinguishes `NotSent` before writer admission from `Unknown` afterward. Timeouts and dropped futures stop local waiting, without retrying or undoing peer work. An EOF before terminal completion is an error; truncation inside a frame is reported separately. Optional incomplete-frame deadlines start at the first byte and do not expire idle connections.\n\nThe `uds` and `named-pipe` features enable `LocalConnector` on their platforms. It uses the endpoint supplied by the caller without inventing names. A custom `Connector` receives an absolute setup deadline and returns an exclusively owned byte stream. `connect_stream` accepts a transport that the caller has already dialed and authenticated.\n\nValidation: `cargo test -p microsandbox-protocol-client --all-features --locked`. The Unix connector test binds a temporary socket and therefore needs an execution environment that permits local socket creation.\n\nProtocols choose whether completed correlation IDs may be reused. Agent connections retire IDs for the lifetime of the connection, matching the relay; control connections may reuse an ID after its terminal response. Exhaustion is a local error and does not reconnect or replay a request.\n","packages/protocol-client/typescript/README.md":"# @microsandbox/protocol-client\n\nA protocol-neutral framed client for TypeScript. `Client<P>` owns one byte transport, correlation IDs, bounded queues, a serialized writer, and response routing. `P` owns setup, its envelope codec, and message gates. Agent and control operations stay in their protocol packages; sandbox lifecycle stays in the SDK.\n\n```text\n                 Client<P>\n          /          |           \\\n native payload   encoded      raw frame / exact packet\n          \\          |           /\n          protocol codec + shared router\n                       |\n                ByteTransport\n```\n\n## Implementing a protocol\n\nAn external package can implement setup and metadata using only public declarations. This example protocol reads one ready byte and uses the shared CBOR envelope. Its server must implement that same handshake.\n\n```ts\nimport {\n  Client, CborEnvelopeCodec, readExactly,\n  type Protocol, type ByteTransport, type EstablishContext, type Established, type SendMetadata,\n} from \"@microsandbox/protocol-client\";\n\nclass ExampleProtocol implements Protocol<number> {\n  async establish(transport: ByteTransport, context: EstablishContext): Promise<Established<number>> {\n    const ready = (await readExactly(transport, 1, context.signal))[0]!;\n    return {\n      transport, ready, codec: new CborEnvelopeCodec(), limits: context.limits,\n      ids: { start: 1, endExclusive: 2 ** 32 },\n    };\n  }\n  prepare(ready: number, _wireName: string): SendMetadata {\n    return { generation: ready, flags: 0 };\n  }\n}\n\nasync function connect(transport: ByteTransport) {\n  return Client.connectTransport(transport, new ExampleProtocol(), { setupTimeoutMs: 10_000 });\n}\n```\n\n`ByteTransport.read(maxBytes)` returns between one and `maxBytes` ordered bytes, or `null` for clean EOF. The engine makes one read at a time and independently serializes writes. `close()` must wake both directions. Passing a transport transfers ownership, including when setup fails. `Connector.connect(context)` supplies repeatable dialing with a deadline and abort signal; it does no discovery itself. `LocalConnector` and `NodeTransport` live at `@microsandbox/protocol-client/node`. Browser-safe `WebSocketConnector` and `WebSocketTransport` are root exports.\n\n`EnvelopeCodec.encodePayload()` supports protocol-specific native encoders. `encode()` wraps supplied payload bytes; `decode()` returns an inspectable `InboundFrame` retaining its `raw` frame. The standard codec uses `{ v, t, p }`, with `p` an actual CBOR byte string, strict unsigned integers, and open message names. Raw subscriptions never invoke that codec.\n\n## Public access levels\n\n| API | Input and result |\n| --- | --- |\n| `request(typedMessage(name, value))` | Native payload; first `InboundFrame` reply |\n| `request(encodedMessage(name, bytes))` | Pre-encoded application payload; first message reply |\n| `requestTyped(request)` | Optional checked request and result decoder; requires terminal reply |\n| `openStream(message)` | Owned native stream through terminal |\n| `requestRaw(flags, body)` / `openStreamRaw(flags, body)` | Opaque envelope bodies and raw replies |\n| `sendOnStream(id, message)` / `sendRaw(id, flags, body)` | Explicit ID owned by this connection |\n| `writeUnchecked(bytes)` | Exact transport bytes without framing or semantic checks |\n| `stream.split()` | Separate owned sender and single receiver |\n| `client.ready` / `clone()` / `close()` | Protocol metadata and shared connection lifetime |\n\nChecked helpers are optional; neither native messages nor raw responses are narrowed to a generated message enum. Application errors remain distinct from `ClientError`. Unknown message names, payload bytes, and envelope fields remain inspectable.\n\n## Limits, ownership, and cancellation\n\nDefault engine limits are 4 MiB per frame, 1024 in-flight exchanges, 256 queued writes, 1024 queued responses per exchange, and 8 MiB of combined engine-owned packet buffering. Protocol setup can select tighter limits. Counts exclude caller-owned inputs, returned frames, and operating-system or browser socket buffers. Native transports read in paused mode; the WebSocket adapter separately caps its input queue and closes on overflow.\n\nA setup deadline covers dialing and protocol establishment. Optional request deadlines cover writer admission and local response waiting; incomplete-frame deadlines start only when a frame begins. Idle connections have no implicit incomplete-frame deadline. `AbortSignal` cancels local waiting. `delivery: \"not_sent\"` means an attempt never entered the writer; `\"unknown\"` means it may have reached the peer. The engine never retries an admitted operation.\n\nClosing a stream only stops local delivery. Nonterminal abandoned exchanges retain their IDs until terminal; stale split senders remain invalid after ID reuse. A receive timeout leaves future frames available. Shared `client.close()` wakes every handle and closes the transport. Close explicitly in `finally`: JavaScript finalization is best-effort, while explicit close is deterministic.\n\n## Development\n\nFrom `packages`, run `npm ci`, `npm run build`, `npm run typecheck`, and `npm test`. Tests exercise an externally defined protocol, full-width IDs, byte preservation, routing, cancellation and admission, ownership, backpressure, transport truncation, WebSocket bounds, and public README examples. Live runtime and historical SDK compatibility tests are additional release requirements.\n\nTo check package exports independently of workspace resolution, install locally packed client packages into a separate consumer directory, then run `node protocol-fixtures/check-packed-protocol-consumer.mjs /path/to/consumer` from `packages`. The consumer lockfile must identify the protocol-client tarball and integrity, and the installed package must not be a workspace symlink. This compiles and executes a custom connector, non-CBOR envelope codec, native/encoded/raw/checked calls, full-width IDs, split streams, explicit sends, and exact packet writes against the installed declarations.\n\nThe command prints a retained output directory. With the packed agent and control packages also installed in that consumer, pass that output directory to `node protocol-fixtures/check-browser-package-roots.mjs /path/to/consumer/protocol-consumer-output`. This bundles all three root exports for the browser, rejects Node builtin imports, and executes the same protocol consumer without Node globals. It verifies browser package loading; actual browser/WebSocket integration remains a separate check.\n\nProtocols choose whether completed correlation IDs may be reused. Agent connections retire IDs for the lifetime of the connection, matching the relay; control connections may reuse an ID after its terminal response. Exhaustion is a local error and does not reconnect or replay a request.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/superradcompany/microsandbox/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nThese instructions are only for agents contributing changes to this repository.\nDo not apply them to other repositories or to general agent behavior outside this contribution workflow.\n\n## Scope\n\n- Follow these guidelines when editing code, docs, tests, examples, CI, or release files for microsandbox.\n- Prefer repository conventions over generic agent habits. When unsure, inspect nearby files and match their style.\n- Do not create branches, commit, push, tag, publish, or open pull requests unless the human explicitly asks.\n- Check `git status --short --branch` before making changes. Do not overwrite or revert user work unless explicitly asked.\n\n## Project Map\n\n- `sdk/rust` is the public Rust SDK crate.\n- `crates/cli` contains the `msb` CLI.\n- `crates/runtime` contains VM runtime integration.\n- `crates/filesystem`, `crates/image`, `crates/network`, `crates/db`, `crates/migration`, `crates/metrics`, `crates/metrics-collector`, `crates/protocol`, and `crates/utils` are shared internal crates.\n- `packages/agent-client` and `packages/microsandbox-types` are the shared agent-protocol client and wire-contract type packages, each with Rust and TypeScript implementations.\n- `crates/agentd` is the in-guest agent. It is a workspace member; the musl guest binary that ships in releases is built separately.\n- `sdk/python`, `sdk/node-ts`, and `sdk/go` contain the language SDKs and native bindings.\n- `docs/` contains the documentation site. Keep docs in sync with user-facing behavior.\n- `examples/` contains runnable examples. Add new example projects only when requested or clearly required by the contribution.\n- `mcp/` and `skills/` are submodules related to agent integrations.\n- `vendor/libkrunfw` is a submodule for the kernel firmware library.\n\nRepository layout:\n\n```text\n.\n|-- AGENTS.md\n|-- Cargo.lock\n|-- Cargo.toml\n|-- COMPATIBILITY.md\n|-- DEVELOPMENT.md\n|-- Dockerfile.agentd\n|-- justfile\n|-- msb-entitlements.plist\n|-- assets/\n|-- crates/\n|   |-- agentd/\n|   |-- cli/\n|   |-- db/\n|   |-- filesystem/\n|   |-- image/\n|   |-- metrics/\n|   |-- metrics-collector/\n|   |-- migration/\n|   |-- network/\n|   |-- protocol/\n|   |-- runtime/\n|   |-- testing/\n|   |   |-- init/\n|   |   |-- macros/\n|   |   `-- utils/\n|   `-- utils/\n|-- docs/\n|   |-- changelog/\n|   |-- cli/\n|   |-- getting-started/\n|   |-- images/\n|   |-- networking/\n|   |-- observability/\n|   |-- recipes/\n|   |-- sandboxes/\n|   |-- sdk/\n|   `-- security/\n|-- examples/\n|   |-- python/\n|   |-- rust/\n|   `-- typescript/\n|-- mcp/\n|   |-- bin/\n|   |-- src/\n|   `-- package.json\n|-- packages/\n|   |-- agent-client/\n|   `-- microsandbox-types/\n|-- packaging/\n|   `-- docker/\n|-- scripts/\n|   `-- smoke/\n|-- sdk/\n|   |-- go/\n|   |-- node-ts/\n|   |-- rust/\n|   `-- python/\n|-- skills/\n|   `-- microsandbox/\n`-- vendor/\n    `-- libkrunfw/\n```\n\n## Design Principles\n\n- Before making or continuing a change that may introduce a regression, breaking change, or backward-compatibility risk identified below, stop and alert the human with the likely impact and affected workflows.\n- Keep changes narrowly scoped to the requested behavior. Avoid drive-by refactors, unrelated formatting, or dependency churn.\n- Treat sandbox isolation, host filesystem access, networking, and secret handling as security-sensitive. Validate inputs at boundaries and avoid exposing host paths, credentials, or ambient privileges.\n- For public APIs, keep the Rust SDK, CLI, Python SDK, Node SDK, Go SDK, docs, and examples consistent when they describe the same capability.\n- Prefer explicit errors with useful context over silent fallbacks.\n\n## Host Path Handling\n\n- For every path input, identify whether it belongs to the local host, a remote backend, or the guest before resolving it. Never resolve cloud or guest paths against the SDK client's working directory.\n- Resolve local host paths against their documented base once, before deferred use or persistence. Retained handles and asynchronous operations must reuse that resolved path, including their final writes. Sandbox and SDK configuration-file inputs use the contributing file's directory, including managed SDK settings.\n- Making a path absolute must preserve its symlink policy. Do not substitute filesystem canonicalization or collapse parent components across symlinks without reviewing the behavior change.\n- Preserve existing persisted relative paths and their legacy startup/resource-inheritance behavior. They have no reliable original base unless it was saved: do not reject, migrate, prompt about, or rewrite them during restart. Capture absolute host inputs only for new sandboxes, including new restore/branch children, without modifying their source sandbox.\n- Path changes need regression coverage with different creation and consumption directories, plus missing targets and symlinks where applicable. Change process cwd only inside an isolated test subprocess. Review sandbox mounts, rootfs paths, TLS files, backend storage roots, snapshots, transfers, and generated commands when adding a new path input.\n\n## Backward Compatibility Review\n\nBackward-compatibility detection is a required part of working on this project. Surface potential compatibility breaks before making or continuing the affected change.\n\nThe review's immediate goal is proactive detection and reporting, not automatically implementing compatibility fixes. Do not silently add compatibility layers, migrations, legacy codecs, fallback paths, or downgrade behavior. When a material risk is found, explain the affected releases, components, persisted artifacts, users or workflows, the likely failure mode, and the available options; then wait for human direction as required by the Design Principles above.\n\nSDK-to-`msb` compatibility is required in both directions for future changes:\n\n- Newer SDKs must continue to work with older `msb` binaries for existing supported workflows.\n- Newer `msb` binaries must continue to work with older SDKs for existing supported workflows.\n- Apply this requirement to every language SDK and the complete SDK/runtime interaction, including binary resolution, launch arguments and JSON, inherited descriptors, startup responses, control and agent protocols, and shared persisted state. Compatibility with an already-running agent alone does not establish launch compatibility.\n- A new feature unavailable in an older peer must be detected and produce a clear unsupported-feature or upgrade-required error, or use an explicitly approved fallback. It must not silently lose requested behavior or break unrelated existing functionality.\n- Do not assume matching package versions or bundled binaries satisfy this requirement. Review independently installed runtimes, including those resolved from `MSB_HOME`, and use cross-version tests or historical fixtures for affected boundaries; same-version tests alone are insufficient evidence.\n\nBidirectional SDK/runtime compatibility does not mean freezing the shared catalog at the oldest installed or running version. Newer SDKs and CLIs may apply validated catalog upgrades while older VM runtimes remain running. Do not add a blanket \"stop all sandboxes before upgrading\" gate or preserve an old catalog solely because an older runtime is present. Distinguish an older VM process continuing its database writes from an older SDK/CLI reopening the catalog and running its own schema-admission checks; test and report these separately rather than treating one as evidence for the other.\n\nFor online catalog upgrades, preserve migration serialization, transaction safety, recovery journals, and active maintenance leases. Evaluate the actual SQL and persisted-data contracts used by older runtimes; neither an additive-looking migration nor a version difference alone proves safety or incompatibility. Live tests must exercise an older VM across the upgrade, verify retained execution and data, and cover new-SDK launch, control, restart, and cleanup through older runtimes. If a concrete incompatible migration is found, report that specific conflict for a decision rather than reintroducing a blanket compatibility blocker.\n\nIf a proposed change may violate either direction, flag it before implementation and wait for human direction; this requirement does not authorize silently building adapters or choosing a breaking change. Any exception or change to the supported compatibility horizon requires explicit human direction. The known v0.6.9 ↔ v0.6.10 launch incompatibility is an accepted historical exception and must not be repaired as part of unrelated work. It does not exempt future changes from this review or requirement.\n\nBy explicit user direction on 2026-09-13, the historical SDK/runtime compatibility target for this work is v0.6.x, with v0.6.0 as the floor, against the current implementation candidate in both directions. Releases older than v0.6.0, including all v0.4.x and v0.5.x releases, are excluded from required compatibility. Preserve their historical results as diagnostic evidence. This supersedes the earlier exact-v0.5.0 exclusion. Existing codecs and capability gates are not removed or changed by this scope decision. The known v0.6.9 ↔ v0.6.10 exception remains unchanged; other v0.6.x failures are not waived.\n\nBefore changing an existing cross-version boundary, determine:\n\n- What older component, binary, sandbox, or persisted state may interact with the change.\n- Which side is the old producer or consumer.\n- Whether failure would be a clean refusal, loss of functionality, stranded state, silent misinterpretation, data loss, or possible corruption.\n- Whether the repository has a fixture or test using an actual older artifact or binary, rather than only same-version round trips.\n\nAlways perform this review when a change affects any of these areas:\n\n- Host-to-guest framing, message names, flags, IDs, payload fields, version negotiation, bootstrap, or lifecycle handshakes.\n- Unix sockets, Windows named pipes, path hashing, relay routing, control messages, inherited file descriptors, signals, or process-launch JSON.\n- SQLite schemas, migration history, persisted configuration, downgrade behavior, journals, leases, or runtime recovery state.\n- ext4, EROFS, VMDK, OCI layers, filesystem metadata, device IDs, mount tags, block paths, or other on-disk formats.\n- Snapshots, archives, manifests, canonical serialization, digests, parent identities, filenames, or atomic publication order.\n- Cache keys, materializer ABIs, layer ordering, whiteouts, hardlinks, xattrs, or content-addressed storage.\n- The runtime, agentd, libkrun, firmware, kernel patches, virtio devices, shared-memory layouts, or package-version coupling.\n- Network address derivation, MACs, interface names, DNS aliases, published ports, TCP/UDP behavior, vsock, SSH, or SFTP behavior.\n- Heartbeats, boot errors, metrics, logs, lock files, runtime directories, or other files consumed across process or release boundaries.\n\nCheck each applicable compatibility direction:\n\n1. A new host interacting with an old running sandbox and old agentd.\n2. A new release opening an existing `MSB_HOME` and its database.\n3. A new release reading old disks, snapshots, archives, caches, and filesystem metadata.\n4. An older release encountering state written by the new release, including downgrade refusal behavior.\n5. Exported artifacts moving between releases, platforms, or architectures.\n6. Independently running components from different releases communicating during an upgrade.\n7. A newer SDK launching and operating an older `msb` binary.\n8. An older SDK launching and operating a newer `msb` binary.\n\nTreat stable strings, numeric constants, paths, hashes, serialized field details, ordering guarantees, timing, and error interpretations as compatibility-sensitive even when they are not part of the public API.\n\nFor changes affecting persisted data or cross-version communication, read [COMPATIBILITY.md](COMPATIBILITY.md) before implementation. Follow its contract-version naming, module ownership, migration, and validation guidelines.\n\n## Path Handling\n\n- Every relative path must have an explicit base directory. Never rely on the working directory at the time of eventual use.\n- Resolve CLI paths against the invocation directory, config-file paths against the config file’s directory, and SDK paths against a documented base.\n- Capture that base and resolve host paths before spawning asynchronous work or passing them to another process.\n- Persist absolute host paths when they reference a fixed local resource that must remain the same across restarts.\n- Distinguish host paths, guest paths, volume-relative paths, and resource identifiers. Do not apply host-path normalization to all strings.\n- Making a path absolute, collapsing `..`, and resolving symlinks are different operations. Choose deliberately; they can select different destinations.\n- Use structured path fields internally. Avoid concatenating paths into delimiter-separated strings that become ambiguous with valid filenames.\n- Return explicit errors when resolution fails. Never silently substitute another directory or panic.\n- Enforce filesystem containment during the operation, accounting for symlinks and concurrent changes. String-prefix checks alone are insufficient.\n- Centralize path-resolution rules so CLI, SDK, and background execution cannot drift.\n\nFor path-related changes, test creation in directory A followed by use from directory B, plus relevant symlink, `..`, missing-path, and platform-specific cases. Assert the exact file or directory accessed—not just whether the operation succeeded.\n\n## Rust Layout And Style\n\n- Most Rust crates use `lib/lib.rs` for library code and `bin/main.rs` for binaries. Keep using those paths for new crate entries unless the surrounding crate already does something different.\n- When adding a new library or binary target, declare the path explicitly in `Cargo.toml`:\n\n```toml\n[lib]\npath = \"lib/lib.rs\"\n\n[[bin]]\nname = \"example\"\npath = \"bin/main.rs\"\n```\n\n- Keep crate roots and module roots thin. `lib.rs` and `mod.rs` should declare modules, crate attributes, and exports only. Put implementation in leaf modules such as `sandbox/config.rs`, `policy/types.rs`, or `commands/run.rs`.\n- File order should be:\n  1. Module docs and crate/file attributes, such as `//! ...` and `#![warn(missing_docs)]`.\n  2. `use` imports.\n  3. Sectioned items.\n- Group imports by origin, separated by blank lines: standard library first, external crates second, then `crate::` and `super::` imports.\n- Do not put `use` statements inside sections unless there is a narrow local reason, such as a test module import.\n- Strongly prefer importing types at the top of the file or test module over spelling out full module paths in signatures, patterns, and function bodies. Use an import alias for name collisions; keep qualified paths when they meaningfully clarify an otherwise ambiguous name.\n- Use the exact section delimiter shown below. Do not invent alternate Markdown-style, shorter, or decorative section headers.\n- Include only sections that contain items. Do not add empty sections just to satisfy the full order.\n- Organize Rust files with these section headers, in this order when applicable:\n\n```rust\n//--------------------------------------------------------------------------------------------------\n// Constants\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Types\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Methods\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Trait Implementations\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Functions\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Macros\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Tests\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Re-Exports\n//--------------------------------------------------------------------------------------------------\n```\n\n- Aggregator files that only expose modules and public items may use `Exports` instead of `Re-Exports` when matching existing files.\n- Use qualified section labels only to split large sections into obvious groups, for example `Types: Identifiers`, `Functions: Handlers`, or `Functions: Helpers`.\n- Do not create a qualified section for one or two items unless the surrounding file already uses that pattern.\n- Put constants and statics under `Constants`.\n- Put `struct`, `enum`, `trait`, and `type` definitions under `Types`.\n- Put inherent `impl Type` blocks under `Methods`, directly after the related type definitions when practical.\n- Put `impl Trait for Type` blocks under `Trait Implementations`.\n- Put free functions under `Functions`. If a free function is only used by one public function, place it later in `Functions: Helpers`.\n- Put macros under `Macros`, not near the call site.\n- Put unit tests under `Tests`, usually as `#[cfg(test)] mod tests`. Keep test-only helpers in the same section.\n- Put public re-exports under `Re-Exports`, or `Exports` in root files that use the existing aggregator style.\n- Keep items in dependency order inside a section: public surface first, private helpers later.\n- Keep docs on public types, fields, methods, functions, and modules. This repo uses `#![warn(missing_docs)]` in public crates, so new public items should explain what they are for.\n- Prefer explicit domain types over loosely typed strings, booleans, or tuples when the value crosses an API or subsystem boundary.\n- Separate logical steps with blank lines: setup, early returns, normalization, validation, operations, and final results. Apply the same spacing to test setup, actions, and assertions; do not rely on `cargo fmt` to provide it.\n- Prefer named intermediate values when an expression combines distinct steps, especially fallible lookups, parsing, and validation. Make each failure point easy to identify.\n- Keep iterator chains simple. Prefer a straightforward loop when nested closures or branching make the logic harder to follow; do not add helpers solely to hide that complexity.\n- Keep straightforward logic together. Extract helpers when they clarify a distinct responsibility or remove meaningful duplication, rather than merely shortening a function.\n- Write comments to explain intent, constraints, or non-obvious behavior rather than narrating the code.\n- Name values for what they currently represent. Avoid names that imply an action has already happened when the value describes a requirement or planned action.\n- During refactors, conflict resolution, bug fixes, and feature work, call out any expected behavior, API, or data-format changes and wait for direction when the risk is material.\n- Use `thiserror` or existing local error patterns for typed errors. Include enough context for callers to understand the failing operation.\n- Keep shared library and SDK errors interface-neutral: describe the problem and remedy without CLI flags or command syntax. Add command-specific guidance in the CLI layer.\n- In async code, avoid holding locks across `.await`. Prefer explicit ownership, short critical sections, and existing Tokio patterns in the surrounding module.\n- Keep feature-gated code close to the feature it gates and use existing `#[cfg(feature = \"...\")]` patterns.\n- Do not add examples under `examples/` unless requested or clearly required. Prefer tests and docs for small usage coverage.\n- Run `cargo fmt` before finalizing Rust changes.\n\n## Development Build Notes\n\n- If you build `msb` to run it locally on macOS, make sure the binary is codesigned with `msb-entitlements.plist`; otherwise VM/runtime failures may be caused by missing entitlements instead of your code change.\n- Prefer `just build` or `just build-msb` when producing a runnable local binary. The macOS recipe rebuilds `msb` and runs:\n\n```bash\ncodesign --entitlements msb-entitlements.plist --force -s - build/msb\n```\n\n- If you bypass `just` and call `cargo build` directly, manually codesign the exact `msb` binary you are going to run before testing sandbox startup, protocol, networking, or filesystem behavior.\n\n## Validation\n\nUse focused checks for the files you touched, then broader checks when the change crosses crate, SDK, CLI, or runtime boundaries.\n\nCommon Rust checks:\n\n```bash\ncargo fmt --all -- --check\ncargo clippy --workspace -- -D warnings\ncargo test --workspace\ncargo build -p microsandbox-cli\n```\n\n`agentd` is a workspace member, so the workspace-wide commands above cover it. The musl guest binary that ships in releases is built separately via `just build-agentd`.\n\nPython SDK checks:\n\n```bash\ncd sdk/python\nuv sync --group dev\nuv run maturin develop --release\nuv run pytest\nuv run ruff check .\n```\n\nNode SDK checks:\n\n```bash\ncd sdk/node-ts\nnpm ci\nnpm run build\nnpm test\nnpm run typecheck\n```\n\nGo SDK checks:\n\n```bash\ncd sdk/go\ngo test -count=1 .\ngo test -tags \"smoke microsandbox_ffi_path\" -count=1 -timeout 2m .\n```\n\nIntegration tests may require Linux with KVM or macOS Apple Silicon support. If a needed check cannot run in the current environment, say exactly which command was skipped and why.\n\nThe full local setup and build loop is documented in `DEVELOPMENT.md`. Use `just setup`, `just build`, and `just install` when you need the full local runtime, `agentd`, or `libkrunfw` artifacts.\n\n## Commits\n\n- Use Conventional Commits for commit titles: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `ci`, or `build`.\n- Use a scope when it clarifies the affected area, for example `fix(network): ...` or `docs(sdk): ...`.\n- Keep the subject imperative, lowercase after the colon, no trailing period, and at most 72 characters.\n- Include a commit body for non-trivial changes. Explain what changed and why.\n- Use signed commits: `git commit -S`.\n- Before committing, inspect the actual diff, including new, modified, and deleted files. Do not write a commit message from filenames or previous commit messages alone.\n- If there is nothing to commit, say so rather than creating an empty commit.\n\nExample:\n\n```text\nfix(network): wake smoltcp after accepted host connections\n\nNotify the network loop after accepting a published-port connection so\npending guest traffic can make progress without waiting for another timer.\n```\n\n## Branches And Pull Requests\n\n- You may make local edits while on `main`, but do not commit directly to `main`. Start from the latest `main` when creating a contribution branch.\n- Use short, descriptive, kebab-case branch names. Avoid personal prefixes in shared documentation unless the maintainer asks for one.\n- Before opening a PR, compare against the intended base branch and inspect the actual diff:\n\n```bash\ngit log origin/main..HEAD --oneline\ngit diff origin/main..HEAD --stat\ngit diff origin/main..HEAD --name-status\n```\n\n- PR titles should follow Conventional Commit style and stay under 72 characters.\n- PR descriptions should be plain and accurate:\n  - `## TL;DR`: one or two short sentences.\n  - `## Description`: a flat bullet list of core changes.\n  - `## Test Plan`: concrete commands or observable checks.\n- Do not use emojis in PR titles or descriptions.\n- If a PR description includes an API example, verify every symbol, path, flag, type, field, and signature against the diff before writing it.\n\n## Version And Release Changes\n\n- Do not bump versions, publish packages, create release tags, or modify release automation unless explicitly asked.\n- All released packages share a version. When a version bump is requested, check `Cargo.toml`, `sdk/node-ts/package.json`, `mcp/package.json`, and any other package metadata touched by the release process in `DEVELOPMENT.md`.\n- For release or version PRs, summarize the user-visible changes since the previous version bump and run the relevant dry-run publish checks when practical.\n\n## Documentation And Examples\n\n- For changes under `docs/`, read [the Microsandbox docs skill](.agents/skills/microsandbox-docs/SKILL.md) for page structure, examples, version labels, and validation.\n- Update docs when behavior, configuration, CLI flags, SDK APIs, or examples change.\n- Keep SDK reference introductions to a short purpose statement and a link to the relevant usage guide, then go straight into API entries. Follow the concise structure of the execution and networking reference pages across all SDK languages.\n- Start reference type sections directly with their members when the type heading is sufficient. Avoid redundant “Built by” / “Used by” rows and introductory text that repeats the member descriptions.\n- Do not prepend feature walkthroughs, lifecycle summaries, implementation details, or long examples to reference pages or type sections. Put API-specific details beside the relevant member and broader explanations in the usage guides; avoid duplicating those guides in the reference.\n- In SDK reference pages, wrap usage examples in a collapsed `<Accordion title=\"Example\">`. Keep API signatures and type definitions visible outside the accordion.\n- When removing reference overviews, preserve unique signatures, constraints, result fields, and resource-cleanup requirements beside the relevant member or in the linked usage guide.\n- Omit field lists that only repeat documented builder options. Keep field references for returned data and properties not covered by the builder.\n- Do not use tables in SDK references. Use individual method and property headings with visible signatures and the existing parameter/return blocks. Group returned hash or record fields in the existing field blocks, including types, descriptions, and defaults where needed. Preserve links and existing anchors, and avoid repeating the same description above and inside a return block.\n- In Rust documentation examples, import types at the top of each snippet and use their short names in the body instead of fully qualified paths. Group standard-library imports before external-crate imports.\n- Keep examples realistic and runnable. Do not invent APIs or flags.\n- Prefer editing existing examples over adding new example projects unless the new example is requested or clearly fills a missing user workflow.\n- Documentation should describe current behavior, not future plans, unless the page is explicitly about roadmap work.\n\n## Agent Operating Rules\n\n- Use `rg` or `rg --files` for repository searches.\n- Read the relevant files before editing. Let existing module boundaries guide the change.\n- Make the smallest coherent change that satisfies the request.\n- Avoid destructive git commands such as `git reset --hard` and `git checkout --` unless explicitly requested.\n- Do not edit generated artifacts, lockfiles, or submodule pointers unless the change requires it.\n- If generated files or lockfiles must change, explain why in the final summary.\n- Report what changed, what validation ran, and any checks that were skipped.\n","isInternal":false,"tokens":5462,"sizeBytes":27841},{"name":"SKILL.md","path":".agents/skills/microsandbox-docs/SKILL.md","rawUrl":"https://raw.githubusercontent.com/superradcompany/microsandbox/HEAD/.agents/skills/microsandbox-docs/SKILL.md","title":"microsandbox-docs","category":"anthropic-skill","format":"markdown","content":"---\nname: microsandbox-docs\ndescription: Edit Microsandbox product, CLI, and SDK documentation with verified behavior, concise examples, and the repository's Mintlify conventions. Use for changes under microsandbox/docs.\n---\n\n# Microsandbox docs\n\nFollow the repository's `AGENTS.md` for contribution rules and SDK reference structure. Read the affected pages and check the working tree before editing. If a target branch is specified, start from its latest commit and preserve unrelated branch changes.\n\n## Verify the feature\n\n- Check implementation, configuration schema, CLI flags, and supported SDKs before naming an option or writing an example. Distinguish per-secret settings from sandbox-wide defaults and secret-policy denials from ordinary network denials.\n- Check the published tag or release before attaching a version pill. A merged change is not evidence that a tagged version contains it. State local-only or cloud limitations only where they affect the documented workflow.\n- Make examples internally complete. When an example expects a JSON HTTP denial response, show the response enablement, JSON format, and applicable message setting together, even if some are current defaults.\n\n## Write the page\n\n- Lead with what the reader can do and the shortest useful example. Put the main code sample on the page; use accordions for limits and secondary details.\n- Use short headings that name the operation. Keep violation actions, host permissions such as placeholder passthrough, and response-message configuration conceptually separate.\n- Use `CodeGroup` for supported language examples when a workflow spans SDKs. Show a CLI example only when an equivalent flag or command exists. Put `sandbox.yml` in a YAML configuration section instead of presenting it as a CLI command.\n- Keep prose independent of language-specific syntax when possible. Put language or CLI details beside their example or in the relevant reference page. Preserve meaningful edge cases without repeating them across pages.\n\n## Validate and deliver\n\n- Run `python3 scripts/check-docs-language-order.py` and `git diff --check`. Parse changed YAML examples, check links and anchors, and load the affected pages in the Mintlify preview when available.\n- Report the changed pages, checks, and any behavior or release ambiguity. Follow the user's requested target branch and publishing route.\n","frontmatter":{"name":"microsandbox-docs","description":"Edit Microsandbox product, CLI, and SDK documentation with verified behavior, concise examples, and the repository's Mintlify conventions. Use for changes under microsandbox/docs."},"isInternal":false,"tokens":447,"sizeBytes":2373},{"name":"README.md","path":"packages/agent-client/README.md","rawUrl":"https://raw.githubusercontent.com/superradcompany/microsandbox/HEAD/packages/agent-client/README.md","title":"agent-client Documentation","category":"plugin-manifest","format":"markdown","content":"# microsandbox agent client\n\nTransport-agnostic clients for speaking the microsandbox agent protocol.\n\nThis layer sits between the protocol package and the high-level SDKs:\n\n```text\nsdk -> agent-client -> protocol-client -> protocol\n```\n\nThe agent package supplies connection handshakes, its existing message codec, and protocol-generation gates. Its public client is a specialization of the shared generic router, which owns IDs, framing, transport lifetime, and request/stream routing. SDK packages own sandbox lifecycle, name resolution, image management, volumes, and metrics.\n\n## Layout\n\n```text\npackages/agent-client/\n├── rust/\n└── typescript/\n```\n\n- `rust/` publishes as `microsandbox-agent-client`.\n- `typescript/` publishes as `@microsandbox/agent-client`.\n\nThe Rust crate has no default transport feature. Local SDK connections opt into `uds` on Unix or `named-pipe` on Windows. An owned `AsyncRead + AsyncWrite` transport can also be supplied, including a caller-authenticated WebSocket adapted to bytes.\n\nThe TypeScript package exposes a browser-safe default entry and keeps Node-only Unix sockets behind `@microsandbox/agent-client/node`; browser/front-end callers use `WebSocketTransport`.\n\n## Message Model\n\nThe public API names protocol boundaries by structure:\n\n- `TypedMessage`: message type plus a native payload object.\n- `EncodedMessage`: message type plus already-CBOR-encoded payload bytes.\n- `RawFrame`: routing fields and an opaque envelope body.\n- `Message` (Rust) / `InboundFrame` (TypeScript): an inspectable envelope retaining its original raw frame.\n- `TransportPacket`: exact transport bytes, including length prefix.\n\nUnchecked packet writes are an explicit escape hatch for relays, tests, and specialized tooling.\n\nRust and TypeScript preserve native, encoded, raw, exact-packet, explicit-ID, and owned split-stream access. See each package README for the API migration and validated examples. Shared TypeScript packages build together through the private npm workspace in `packages`.\n","isInternal":false,"tokens":412,"sizeBytes":2030},{"name":"README.md","path":"packages/agent-client/rust/README.md","rawUrl":"https://raw.githubusercontent.com/superradcompany/microsandbox/HEAD/packages/agent-client/rust/README.md","title":"rust Documentation","category":"plugin-manifest","format":"markdown","content":"# microsandbox-agent-client\n\n`AgentClient` is `Client<AgentProtocol>` from `microsandbox-protocol-client`. The agent package supplies relay setup and generation metadata; the shared engine supplies one reader, writer, allocator, and ownership registry. SDK services such as execution collection and filesystem handles remain in `microsandbox`.\n\n```text\nTypedMessage / EncodedMessage / raw envelope / exact packet\n                           |\n                 Client<AgentProtocol>\n                           |\n          current or legacy relay handshake\n                           |\n                       agent relay\n```\n\n## Native, encoded, and raw messages\n\n```rust,no_run\nuse microsandbox_agent_client::{AgentClient, TypedMessage, EncodedMessage};\nuse microsandbox_protocol::{fs::{FsOp, FsRequest, FsResponse}, message::MessageType};\n\nasync fn inspect(client: &AgentClient) -> Result<(), Box<dyn std::error::Error>> {\n    let request = FsRequest {\n        bulk: None,\n        op: FsOp::Stat { path: \"/etc/os-release\".into(), follow_symlink: true },\n    };\n    let reply = client.request(TypedMessage::new(MessageType::FsRequest, &request)).await?;\n    let response: FsResponse = reply.payload()?;\n    println!(\"{}\", response.ok);\n\n    // The caller's payload bytes are not parsed or normalized by this path.\n    let payload = vec![0xa0];\n    let _reply = client.request(EncodedMessage::new(MessageType::Ping, payload)).await?;\n    Ok(())\n}\n```\n\n`Message.t` is the actual wire string. Unknown names and unknown envelope fields remain inspectable: `Message::raw()` borrows the original frame and `into_raw()` returns it. Ordinary requests surface peer error messages as messages. Optional `Request<AgentProtocol>` implementations add checked unary decoding without narrowing the generic API.\n\n## Owned streams and explicit IDs\n\n```rust,no_run\nuse microsandbox_agent_client::{AgentClient, TypedMessage};\nuse microsandbox_protocol::{exec::{ExecRequest, ExecStdin}, message::MessageType};\n\nasync fn exchange(client: &AgentClient, request: &ExecRequest) -> Result<(), Box<dyn std::error::Error>> {\n    let stream = client.stream(TypedMessage::new(MessageType::ExecRequest, request)).await?;\n    let id = stream.id();\n    let (sender, mut receiver) = stream.into_parts();\n    client.send(id, TypedMessage::new(MessageType::ExecStdin, ExecStdin { data: vec![] })).await?;\n    sender.send(TypedMessage::new(MessageType::ExecStdin, ExecStdin { data: b\"input\".to_vec() })).await?;\n    while let Some(message) = receiver.recv().await? {\n        println!(\"{}\", message.t);\n    }\n    Ok(())\n}\n```\n\nBoth split parts retain the connection and ID lease; sender handles may be cloned and there is one consuming receiver. Terminal receipt disables sends and delivers the terminal frame once. Closing or dropping the receiver disables sends and retains bounded drain state until terminal completion. It sends no process signal or filesystem EOF. Transport loss before a terminal frame is an error, distinct from clean exhaustion.\n\n`request_raw(flags, body)`, `stream_raw(flags, body)`, and `send_raw(id, flags, body)` exchange opaque envelopes without CBOR decoding. `write_unchecked(packet_bytes)` serializes exact caller bytes without allocating an ID or subscribing for replies. `TransportPacket` remains an optional standalone framing helper; `packet.into_bytes()` feeds the exact write API. Standalone packet readers require exclusive transport ownership.\n\n## Setup and metadata\n\nEnable `uds` for native Unix sockets or `named-pipe` for Windows pipes. `connect_stream` accepts an owned `AsyncRead + AsyncWrite + Unpin + Send` transport, including `UdsTransport` and `NamedPipeTransport`. The `stream` feature name remains accepted for existing manifests; generic owned transports are available without additional dependencies.\n\n```rust,no_run\nuse microsandbox_agent_client::AgentClient;\nuse std::time::Duration;\n\nasync fn connect(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {\n    let client = AgentClient::connect_with(path, |o| o.setup_timeout(Duration::from_secs(5))).await?;\n    println!(\"{}\", client.ready().agent_version());\n    println!(\"{}\", client.ready().negotiated_version);\n    println!(\"{} ready bytes\", client.ready().ready_bytes().len());\n    client.close().await;\n    Ok(())\n}\n```\n\nSetup has one deadline across dial and handshake. The current `[id_min,id_max]` relay prologue and the supported pre-0.5 `[id_offset]` prologue retain their existing parsing, ID ranges, and ready payload behavior. Known operations use the smaller host/peer generation as their availability gate. Current connections continue emitting the host's existing envelope generation; legacy connections emit generation one. Source-level API changes do not retire the legacy wire path.\n\n## Migration from the previous active client\n\n| Previous API | Shared-client API |\n| --- | --- |\n| `connect(path)` / `connect_stream(stream)` | Same entry points; owned streams no longer require the `stream` feature |\n| `request(type, &payload)` | `request(TypedMessage::new(type, &payload))` |\n| `stream(type, &payload) -> (id, receiver)` | `stream(TypedMessage::new(type, &payload))`, then `id()` / `into_parts()` |\n| `send(id, type, &payload)` | `send(id, TypedMessage::new(type, &payload))` |\n| `request_raw(flags, body)` | Same call; retains drain state after a nonterminal first reply |\n| `stream_raw(flags, body) -> (id, receiver)` | Owned raw stream, then `id()` / `into_parts()` |\n| `send_raw(id, flags, body)` | Same call, requiring a live owned ID |\n| `connect_with_timeout(path, duration)` | `connect_with(path, |o| o.setup_timeout(duration))` |\n| `connect_stream_with_timeout(stream, duration)` | `connect_stream_with(stream, |o| o.setup_timeout(duration))` |\n| `connect_with_deadline(path, deadline)` | `connect_with(path, |o| o.setup_timeout(deadline.saturating_duration_since(Instant::now())))` |\n| `connect_stream_with_deadline(stream, deadline)` | `connect_stream_with(stream, |o| o.setup_timeout(deadline.saturating_duration_since(Instant::now())))` |\n| `ready()` | `ready().agent` |\n| `ready_bytes()` | `ready().ready_bytes()` |\n| `negotiated_version()` / `supports(type)` | `ready().negotiated_version` / `ready().supports(type)` |\n| `protocol()` / `is_legacy_protocol()` | `ready().wire_format` / `ready().is_legacy_protocol()` |\n| `agent_version()` | `ready().agent_version()` |\n| `ensure_version_compat(type)` | `AgentProtocol::ensure_version_compat_for(type, client.ready().negotiated_version)` |\n| `AgentClient::ensure_version_compat_for(type, generation)` | `AgentProtocol::ensure_version_compat_for(type, generation)` |\n| Unwired `AgentStream<T>` / packet-only `AgentTransport` | Active `AgentStream` / owned byte transports accepted directly by `connect_stream` |\n| Connection close by ownership transfer | Shared `close(&self)`; last client/stream owner also closes the transport |\n\n`ClientError.delivery` distinguishes `NotSent` from `Unknown` after writer admission. Requests are never automatically replayed. Raw stream access, known-ID sends, custom transports, ready bytes, dynamic names, and encoded payloads remain available without adopting SDK domain objects.\n\nDeadline conversions use `tokio::time::Instant`, matching the original absolute-deadline API. Keep the conversion inside the options closure so it uses the remaining duration when setup begins. Known native and encoded sends perform the same availability check automatically; the public `AgentProtocol` helper supports callers retaining only the generation. Unsupported operations now use the shared `ClientError` category rather than the previous agent-specific error fields.\n\nValidation: `cargo test -p microsandbox-agent-client --all-features --locked`. Unix-socket tests require permission to bind temporary local sockets. The migrated historical handshake cases are source-level regression tests; they do not replace live historical runtime/SDK validation.\n\nThe SDK retains an internal optimized client for generation-eight bulk transfers and Unix shared arenas. Its existing bulk APIs remain available; the generic client is the public transport-independent framed API.\n","isInternal":false,"tokens":1771,"sizeBytes":8161},{"name":"README.md","path":"packages/agent-client/typescript/README.md","rawUrl":"https://raw.githubusercontent.com/superradcompany/microsandbox/HEAD/packages/agent-client/typescript/README.md","title":"typescript Documentation","category":"plugin-manifest","format":"markdown","content":"# @microsandbox/agent-client\n\nLow-level TypeScript access to the microsandbox agent protocol. `AgentClient` is `Client<AgentProtocol>` from `@microsandbox/protocol-client`; the agent package supplies relay setup, its existing encoder, and generation gates. The shared engine owns framing, IDs, queues, cancellation, and transport lifetime. Sandbox lifecycle and process/filesystem convenience APIs belong to the SDK.\n\n```text\nAgentProtocol (setup, encoder, gates)\n                 |\n          Client<AgentProtocol>\n            /          \\\n native / encoded     raw / exact packets\n                 |\n       Unix socket / named pipe / WebSocket\n```\n\n## Installation and entry points\n\nInstall with `npm install @microsandbox/agent-client`. Node.js 22 or newer is required for native transports. The root entry is browser-safe. `@microsandbox/agent-client/node` imports Node's `net` module and accepts native endpoint paths verbatim, including Windows named-pipe names.\n\n## Native messages\n\n```ts\nimport { connectUnix } from \"@microsandbox/agent-client/node\";\nimport { typedMessage } from \"@microsandbox/agent-client\";\n\nconst client = await connectUnix(\"/tmp/msb-agent.sock\", { setupTimeoutMs: 10_000 });\ntry {\n  const response = await client.request(typedMessage(\"core.fs.request\", {\n    op: { Stat: { path: \"/etc/os-release\", follow_symlink: true } },\n  }), { requestTimeoutMs: 5_000 });\n  console.log(response.type, response.decodePayload());\n  console.log(client.ready.agentVersion, client.ready.negotiatedVersion);\n} finally {\n  await client.close();\n}\n```\n\n`typedMessage(name, value)` asks the agent codec to encode a native payload. `encodedMessage(name, bytes)` supplies only the application payload; it does not supply an envelope or framed packet. Both paths apply known message gates and flags. Names are open strings, so extensions remain accessible without editing an enum. These methods do not validate an application schema. `requestTyped()` accepts an optional checked request with its own result decoder and requires a terminal response.\n\nThe current TypeScript agent encoder continues to emit generation-five envelopes, including its existing CBOR map and byte-string representations. The ready generation controls feature gates. The supported pre-0.5 relay prologue selects generation-one envelopes and rejects unavailable filesystem/TCP operations before sending. The captured ready frame and unknown fields remain available through `client.ready.frame` and `client.ready.readyBytes`.\n\n## Browser streams\n\n```ts\nimport { AgentClient, WebSocketConnector, typedMessage } from \"@microsandbox/agent-client\";\n\nconst client = await AgentClient.connectConnector(\n  new WebSocketConnector(\"wss://relay.example.com/agent\"),\n  { setupTimeoutMs: 10_000 },\n);\ntry {\n  const stream = await client.openStream(typedMessage(\"core.exec.request\", {\n    cmd: \"sh\", args: [\"-lc\", \"echo hello\"],\n  }));\n  try {\n    for await (const frame of stream) {\n      console.log(frame.type, frame.payload);\n    }\n  } finally {\n    stream.close();\n  }\n} finally {\n  await client.close();\n}\n```\n\nThe relay must forward the agent's binary byte stream, including its prologue. `WebSocketTransport` also wraps a caller-constructed socket for custom authentication. Its incoming queue defaults to 8 MiB and 4096 messages; overflow closes the connection because browser WebSockets cannot pause incoming messages. Outgoing writes wait for the browser's send queue to drain. The connector and handshake share one setup deadline.\n\n## Raw and exact access\n\n```ts\nimport { type AgentClient, TransportPacket } from \"@microsandbox/agent-client\";\n\nasync function exchangeOpaque(client: AgentClient, envelope: Uint8Array) {\n  const stream = await client.openStreamRaw(0, envelope);\n  const { sender, receiver } = stream.split();\n  try {\n    await sender.send(0, envelope);\n    await client.sendRaw(sender.id, 0, envelope);\n    return await receiver.next({ requestTimeoutMs: 5_000 });\n  } finally {\n    receiver.close();\n    sender.close();\n  }\n}\n\nasync function forwardExactPacket(client: AgentClient, bytes: Uint8Array) {\n  await client.writeUnchecked(TransportPacket.fromBytes(bytes));\n}\n```\n\nRaw APIs leave envelope and payload bytes opaque. `requestRaw(flags, body)` returns the first raw reply. `openStreamRaw()` receives through the terminal reply. `sendRaw(id, flags, body)` requires an active ID owned by this connection. A split sender retains its original lease and cannot regain permission if the numeric ID is reused. `writeUnchecked(bytes)` can write even deliberately malformed transport bytes; `TransportPacket.fromBytes()` optionally validates a single packet first.\n\nNative replies retain the complete original raw frame as `frame.raw`, including unknown envelope fields. `core.error` is an ordinary terminal agent response; interpreting it belongs to the caller or a checked request decoder.\n\n## Ownership and failures\n\nStreams own their send and receive halves. `split()` transfers those halves and invalidates the original stream's send/receive methods. Closing a receiver or leaving async iteration stops local delivery; it sends no remote cancel, signal, or EOF. An abandoned ID remains draining until its terminal frame arrives. A timed-out `next()` does not consume the next arriving frame.\n\n`client.clone()` shares the connection. `client.close()` closes every shared handle and wakes pending operations. Explicitly close clients and streams: JavaScript garbage collection only offers best-effort cleanup. A request deadline covers writer admission and the local response wait; it does not cancel remote execution. `ClientError.delivery` is `not_sent` before admission and `unknown` afterward. Nothing automatically retries an admitted operation.\n\n## Migration from the previous package API\n\n| Previous surface | Shared-engine surface |\n| --- | --- |\n| Independent `AgentClient` class/router | `AgentClient = Client<AgentProtocol>` plus convenience constructors |\n| `handshakeTimeoutMs` | `setupTimeoutMs`, covering dial and handshake together |\n| `client.negotiatedVersion()` | `client.ready.negotiatedVersion` |\n| Closed `MessageType` parameter | Open string names; `MessageType` still lists known names |\n| Packet-oriented custom `AgentTransport` | Ordered `read(maxBytes)`, `write(bytes)`, `close()` byte transport |\n| `InboundFrame.fromRawFrame(frame)` | `new AgentEnvelopeCodec().decode(frame)` |\n| `new AgentStream(...)` | Obtain owned streams from `client.openStream()` or `openStreamRaw()` |\n| Request, encoded payload, stream, exact packet methods | Retained on the generic client, with additional raw and split interfaces |\n\nThese are public source API changes. Existing agent wire behavior is preserved; source and byte tests do not replace the required historical runtime/SDK launch tests.\n\n## Development\n\nFrom the repository's `packages` directory, run `npm ci`, `npm run build`, `npm run typecheck`, and `npm test`. The private workspace links the shared package locally; published dependency metadata uses the release version. The README's TypeScript examples are checked with the public package declarations.\n","isInternal":false,"tokens":1474,"sizeBytes":7123},{"name":"README.md","path":"packages/control-client/rust/README.md","rawUrl":"https://raw.githubusercontent.com/superradcompany/microsandbox/HEAD/packages/control-client/rust/README.md","title":"rust Documentation","category":"plugin-manifest","format":"markdown","content":"# Rust control client\n\n`ControlConnection` discovers whether an existing control endpoint supports framed CBOR or legacy JSON. `ControlClient` is the explicitly framed `Client<ControlProtocol>` entry point, and `JsonControlClient` is an explicit legacy unary adapter. Their checked helpers share the same operation records.\n\n```text\nControlConnection -- JSON capabilities --+-- advertised CBOR: fresh stream + hello\n                                        |                      |\n                                        |               ControlClient\n                                        |\n                                        +-- legacy: JsonControlClient\n```\n\nAutomatic discovery has one deadline across the probe, redial, and hello. It selects CBOR only after a valid affirmative capability response. Malformed replies, arbitrary peer errors, empty EOF, and failed framed setup are errors. There is no error-string downgrade or mutation replay. `ControlClient` sends its hello directly when callers already know the peer supports framed control.\n\n```rust,no_run\nuse microsandbox_control_client::{\n    ControlConnection, ControlMessageType, ControlReply, Empty, GetMemoryState, TypedMessage,\n};\n\nasync fn inspect(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {\n    let connection = ControlConnection::connect(path).await?;\n    let reply = connection.request(TypedMessage::new(ControlMessageType::MemoryState, Empty {})).await?;\n    match reply {\n        ControlReply::Framed(message) => println!(\"{}\", message.t),\n        ControlReply::Json(reply) => println!(\"{} original JSON bytes\", reply.raw().len()),\n    }\n    println!(\"{} MiB\", connection.request_typed(&GetMemoryState).await?.target_mib);\n    connection.close().await;\n    Ok(())\n}\n```\n\nIn framed mode, clones reuse one connection. Automatic JSON mode rediscovers before each fresh exchange when its connector cannot prove runtime identity. An owner implementing `VerifiedControlConnector` verifies the connected peer and the runtime's OS birth token on every dial, then rechecks the active run before each operation; `connect_verified_connector` can reuse that owner's JSON selection. A path or PID alone is insufficient. A detected change invalidates the handle before sending instead of silently retargeting the request. The runtime owner, such as an SDK backend, supplies that identity implementation; the protocol package does not claim to verify it itself.\n\n`JsonControlClient::new(path)` and `from_connector(...)` are inert constructors for callers explicitly choosing legacy mode. Its `request` translates only known native requests and returns the actual `JsonReply`, including `ok:false` replies. Checked helpers report `LegacyRemote` with the original reply and unknown batch progress. JSON numbers retain their original tokens; checked memory fields use `u64` without a floating-point intermediate. `connection.framed()` fails locally in JSON mode, and encoded-payload input is rejected there before dialing. Raw streams and exact packets remain available on the explicitly framed client.\n\n`connection.capabilities()` borrows the released generation-1 discovery projection without network I/O, while `connection.runtime_capabilities()` exposes the complete discovered facility set. `GetCapabilities` remains available for a fresh generation-1-compatible observation and `GetRuntimeCapabilities` requests the complete record on generation 2. `closed().await` observes shared closure, including an idle framed disconnect; a successful JSON exchange ending in EOF does not close the session.\n\nGeneration 1 remains the released capabilities, memory, CPU, and secrets surface. Generation 2 adds `CreateCheckpoint`, `CreateDiskCheckpoint`, `CreateBranch`, `PauseRuntime`, `ResumeRuntime`, `GetPauseState`, `GrowRootDisk`, and `CompactDisks`. `ControlConnection` talking to a framed generation-1 runtime selects the historical JSON representation for these checked operations before sending anything; it never retries a mutation after a framed failure. The explicit `ControlClient` instead returns a not-sent unsupported-operation error. Linux descriptor-backed branch creation remains an SDK-level JSON/`SCM_RIGHTS` exception because ordinary framed messages cannot transfer file descriptors.\n\nEnable `uds` for Unix endpoint connections or `named-pipe` for Windows endpoint connections. Caller-owned transports and connectors can be used without either native feature. Endpoint paths are accepted verbatim.\n\n```rust,no_run\nuse microsandbox_control_client::{\n    ControlClient, GetMemoryState, SetMemoryTarget, size::SizeExt,\n};\n\nasync fn resize(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {\n    let client = ControlClient::connect(path).await?;\n    let before = client.request_typed(&GetMemoryState).await?;\n    let accepted = client.request_typed(&SetMemoryTarget::new(2048.mib())).await?;\n    println!(\"{} -> {} MiB\", before.target_mib, accepted.target_mib);\n    client.close().await;\n    Ok(())\n}\n```\n\nMemory wire records retain `u64` values. The existing size helpers retain their conversion rules; direct `SetMemoryTarget { total_mib }` construction provides full-width wire input. Target responses report acceptance and current observations, not guest convergence. Ordered secret batches preserve partial completion and the index of the first failed entry.\n\nThe client keeps `request`, `stream`, `request_raw`, `stream_raw`, explicit-ID `send`/`send_raw`, exact `write_unchecked`, owned `into_parts`, and checked `request_typed` operations. Generic requests return peer error frames as messages; checked helpers interpret them and retain the original response. No sandbox, execution, filesystem, or convergence service is required to use any of these paths.\n\n```rust,no_run\nuse microsandbox_control_client::{ControlClient, EncodedMessage};\n\nasync fn inspect(client: &ControlClient, payload: Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {\n    let response = client.request(EncodedMessage::new(\"extension.inspect\", payload)).await?;\n    println!(\"{}: {} payload bytes\", response.t, response.p.len());\n    Ok(())\n}\n```\n\nSetup defaults to one ten-second deadline and requests to a thirty-second local wait. Request expiry does not cancel remote work or trigger replay. `ClientError` retains `NotSent` versus `Unknown` delivery. Clones and owned streams share a connection; `close` closes it for all owners.\n\nRun `cargo test -p microsandbox-control-client --all-features --locked`. For live tests, set `MSB_CONTROL_TEST_SOCKET` and `MSB_CONTROL_TEST_MODE=cbor` or `json`. The explicitly framed test requires CBOR; run only `live_automatic_discovery_and_explicit_json -- --ignored --exact` against a JSON-only runtime. Use bounded disposable fixtures (512 MiB and two CPUs), and run live tests serially. The automatic test changes targets and restores them through explicit JSON. Optional `MSB_CONTROL_TEST_SECRET` names a dummy fixture initially set to `before` and allowed for `example.invalid`; the test exercises legacy partial-failure reporting and restores that fixture. Skipped live tests are not runtime or platform validation.\n","isInternal":false,"tokens":1436,"sizeBytes":7156},{"name":"README.md","path":"packages/control-client/typescript/README.md","rawUrl":"https://raw.githubusercontent.com/superradcompany/microsandbox/HEAD/packages/control-client/typescript/README.md","title":"typescript Documentation","category":"plugin-manifest","format":"markdown","content":"# TypeScript control client\n\n`ControlClient` is `Client<ControlProtocol>` over the shared framed engine. It sends a bounded `control.hello` directly, negotiates generation and limits, and exposes the original welcome frame. Use it when the peer is known to support framed control. It does not probe JSON or downgrade after failure.\n\nThe root entry is browser-safe and accepts a caller-owned byte transport or reconnectable connector. The `/node` entry accepts an existing Unix socket or Windows named-pipe path without renaming it.\n\n```ts\nimport { GetMemoryState, MiB, SetMemoryTarget, typedMessage } from \"@microsandbox/control-client\";\nimport { connectFramedControl } from \"@microsandbox/control-client/node\";\n\nasync function inspectAndResize(path: string) {\n  const client = await connectFramedControl(path, { setupTimeoutMs: 10_000 });\n  try {\n    const response = await client.request(typedMessage(\"control.memory.state\", {}));\n    console.log(response.type, response.raw.body);\n    const before = await client.requestTyped(new GetMemoryState());\n    const accepted = await client.requestTyped(new SetMemoryTarget(MiB(2048)));\n    console.log(before.target_mib, accepted.target_mib); // bigint, even for small values\n  } finally {\n    await client.close();\n  }\n}\n```\n\nGeneration-1 checked helpers cover capabilities, memory state/target, CPU state/target, and ordered secret updates. Generation 2 adds `GetRuntimeCapabilities`, `CreateCheckpoint`, `CreateDiskCheckpoint`, `CreateBranch`, `PauseRuntime`, `ResumeRuntime`, `GetPauseState`, `GrowRootDisk`, and `CompactDisks`. They validate replies and retain the actual response on `ControlClientError`. Generic message requests return peer error frames directly. A target reply reports acceptance and observation; it does not establish guest convergence. Secret batches stop at the first failure and retain earlier completed changes.\n\nUse `connectControl` for automatic discovery on the same endpoint. It sends the existing read-only JSON capabilities request, closes that exchange, and opens a fresh framed connection only after an affirmative CBOR advertisement. Setup has one total deadline across both connections. Malformed discovery, timeout, EOF without a reply, or a failed welcome never trigger fallback or replay.\n\n```text\nconnectControl(path)\n    |\n    +-- JSON capabilities --> valid legacy reply --> fresh JSON exchange per operation\n    |\n    +-- JSON capabilities --> advertises CBOR -----> fresh hello/welcome --> shared framed client\n```\n\n```ts\nimport { GetMemoryState, MiB, SetMemoryTarget, typedMessage } from \"@microsandbox/control-client\";\nimport { connectControl } from \"@microsandbox/control-client/node\";\n\nasync function compatibleResize(path: string) {\n  const connection = await connectControl(path);\n  try {\n    const reply = await connection.request(typedMessage(\"control.memory.state\", {}));\n    if (reply.kind === \"cbor\") console.log(reply.frame.raw.body);\n    else console.log(reply.reply.raw, reply.reply.value.get(\"memory\"));\n    const before = await connection.requestTyped(new GetMemoryState());\n    const accepted = await connection.requestTyped(new SetMemoryTarget(MiB(2048)));\n    return { before, accepted };\n  } finally {\n    await connection.close();\n  }\n}\n```\n\n`ControlConnection.connectConnector` accepts a repeatable caller connector from the browser-safe root. The connection exposes the read-only generation-1 `capabilities` projection, the complete `runtimeCapabilities` discovery snapshot, `mode`, `clone()`, `isClosed()`, `close()`, `request()`, and `requestTyped()`. `framed()` returns the complete generic surface in CBOR mode and fails locally with `unsupported_mode` in JSON mode. Encoded messages likewise fail locally in JSON mode; the adapter translates only known, validated native operations.\n\nWhen a framed peer selects generation 1, `ControlConnection.requestTyped()` sends generation-2 checked operations through their historical JSON form before any mutation is admitted. An unverified connector rediscovers the endpoint format first. This path is never a retry after a CBOR failure. The explicit `ControlClient` rejects those operations locally against a generation-1 welcome. Linux descriptor-backed branch creation remains outside ordinary CBOR because it requires `SCM_RIGHTS`; generation 2 covers direct local branching without descriptor transfer.\n\n`JsonReply` contains the original response line, including its delimiter and whitespace, and a lossless `Map` of fields. `JsonNumber.token` retains each original numeric token; checked memory observations become `bigint` without passing through a JavaScript number. Ordinary requests return `ok:false` replies. Checked helpers raise `legacy_remote` with the original reply and unknown batch progress. Neither arbitrary diagnostic strings nor legacy errors are converted into structured CBOR errors.\n\nFor explicit legacy access, construction is inert and performs no discovery:\n\n```ts\nimport { GetCpuState } from \"@microsandbox/control-client\";\nimport { jsonControl } from \"@microsandbox/control-client/node\";\n\nasync function inspectLegacy(path: string) {\n  const client = jsonControl(path);\n  try {\n    return await client.requestTyped(new GetCpuState());\n  } finally {\n    await client.close();\n  }\n}\n```\n\nThe equivalent root API is `JsonControlClient.fromConnector(connector, options?)`. Ordinary legacy request and response lines keep their existing size contract; only automatic discovery replies have a 64 KiB limit. Preparing a secret batch snapshots its entries without imposing the framed 4 MiB ceiling on JSON. JavaScript strings remain subject to garbage collection; explicit buffer clearing is not a guarantee that all copies have been erased.\n\nA standalone automatic JSON connection rediscovers before each fresh operation because an endpoint path cannot establish process identity. `ControlConnection.connectVerifiedConnector` permits discovery reuse when the caller supplies a `VerifiedControlConnector`: `connect` must verify every connected peer against the saved OS process birth identity, and `verifySession` must check the active run and process before each operation. Both receive a deadline and cancellation context. Runtime replacement raises `runtime_changed` before mutation admission and closes the shared handle. This extension point does not implement an SDK database or OS identity verifier by itself.\n\n`MiB`, `GiB`, `KiB`, `TiB`, and `Mebibytes` are exported from `@microsandbox/types/size`. Their arithmetic matches the SDK helpers. `SetMemoryTarget` validates integer precision and range before converting to the wire quantity; it also accepts `bigint` for full-width values.\n\nAll generic operations remain available:\n\n| Need | Method |\n|---|---|\n| Native or encoded payload | `request(typedMessage(...))`, `request(encodedMessage(...))` |\n| Exact opaque envelope | `requestRaw(flags, body)` |\n| Message or raw subscription | `openStream(...)`, `openStreamRaw(...)` |\n| Owned sender and receiver | `stream.split()` |\n| Follow-up using an owned ID | `sendOnStream(id, ...)`, `sendRaw(id, ...)` |\n| Exact packet without a subscription | `writeUnchecked(bytes)` |\n| Optional checked unary operation | `requestTyped(request)` |\n| Shared connection ownership | `clone()`, `close()` |\n\n```ts\nimport { ControlClient, encodedMessage, type ByteTransport } from \"@microsandbox/control-client\";\n\nasync function inspectExtension(transport: ByteTransport, payload: Uint8Array) {\n  const client = await ControlClient.connectTransport(transport);\n  try {\n    const reply = await client.request(encodedMessage(\"extension.inspect\", payload));\n    return { name: reply.type, payload: reply.payload, original: reply.raw };\n  } finally {\n    await client.close();\n  }\n}\n```\n\nThe default control request deadline is 30 seconds. Expiry abandons the local wait; it does not cancel remote work or retry it. `ClientError.delivery` distinguishes requests that were not admitted from unknown outcomes. Complete idle connections do not expire automatically. Explicit close closes every clone; JavaScript finalization is only best-effort cleanup.\n\nFrom `packages`, run `npm run build`, `npm run typecheck`, and `npm test`. Live validation is deliberately opt-in: set `MSB_CONTROL_TEST_SOCKET` to a disposable framed runtime's socket and run `npm test -w @microsandbox/control-client`. Add `MSB_CONTROL_TEST_MODE=cbor` to run automatic discovery as well. The package serializes test files because live files mutate and restore the same VM's targets. A skipped live test is not platform or runtime compatibility evidence.\n\nFor a historical JSON-only runtime, set `MSB_CONTROL_TEST_MODE=json` and run only the compatibility live file with `npm exec -w @microsandbox/control-client -- vitest run tests/compatibility-live.test.ts`. It checks automatic and explicit JSON access, concurrent reads, full-width result types, and accepted memory/CPU targets, then restores the original targets. These running-runtime tests do not establish SDK launch compatibility.\n\nThe additional secret test requires `MSB_CONTROL_TEST_SECRET` naming a dummy fixture initially set to `before` and allowed for `example.invalid`. It exercises partial completion, malformed batches, and legacy JSON failures, then restores those fixture values.\n","isInternal":false,"tokens":1886,"sizeBytes":9290},{"name":"README.md","path":"packages/microsandbox-types/README.md","rawUrl":"https://raw.githubusercontent.com/superradcompany/microsandbox/HEAD/packages/microsandbox-types/README.md","title":"microsandbox-types Documentation","category":"plugin-manifest","format":"markdown","content":"# microsandbox types\n\nShared task and wire contract types for microsandbox.\n\nThese packages define the backend-neutral shapes that describe what a sandbox should be: its rootfs source, resources, mounts, patches, network, lifecycle, and the cloud HTTP request/response bodies that carry those specs over the wire. Everything that has to agree on a sandbox's shape (the Rust SDK, the CLI, the cloud API, and the TypeScript front end) depends on these contracts instead of redefining them.\n\nThis is a contract layer, not a runtime. It does not create, start, or talk to sandboxes. It only models the data those operations exchange.\n\n## Layout\n\n```text\npackages/microsandbox-types/\n├── rust/\n└── typescript/\n```\n\n- `rust/` publishes as `microsandbox-types` (crate name `microsandbox_types`). It is the source of truth for every shared type.\n- `typescript/` publishes as `@microsandbox/types`. Its `src/cloud.ts` (entry) and `src/domain.ts` are generated from the Rust types, never hand-edited; only the cloud contract and the domain types it references are emitted.\n\n## Source Of Truth\n\nThe Rust crate owns the definitions. The TypeScript bindings are derived from them with [`ts-rs`](https://github.com/Aleph-Alpha/ts-rs) behind the crate's `ts` feature, so the two stay byte-for-byte aligned.\n\n```text\nrust/lib/*.rs  ──(ts-rs)──▶  typescript/src/{cloud,domain}.ts\n```\n\nTo regenerate the bindings after changing a Rust type:\n\n```bash\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate\n```\n\nCI runs the same generator with `--check` and fails when the checked-in bindings drift:\n\n```bash\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate -- --check\n```\n\n## What Lives Here\n\n- Sandbox specs: `SandboxSpec`, `SandboxResources`, `SandboxRuntimeOptions`, rootfs sources, mounts, patches, init, lifecycle policy.\n- Networking intent: `NetworkSpec`, published ports, protocols.\n- Volumes and snapshot manifests: `VolumeSpec`, `SnapshotSpec`, and their kinds.\n- Exec and logging: `Rlimit`, `RlimitResource`, `LogSource`, `SandboxLogLevel`.\n- Cloud wire contracts: the source-tagged `CloudCreateSandboxRequest` union,\n  `CloudSandbox`, snapshot resources and operations, and\n  paginated/message/error bodies.\n\nCloud snapshot contracts distinguish the durable resource from the asynchronous\ncapture operation:\n\n- `CloudCreateSnapshotRequest` is discriminated by `kind`. Its current `disk`\n  variant captures the writable disk, and the enum can gain other snapshot\n  kinds without changing the envelope shape.\n- `CloudSnapshot` uses the same kind discriminator and includes its canonical\n  manifest, byte size, labels, and `CloudSnapshotLocation`.\n- `CloudSnapshotLocation` is either a managed artifact ID or a host-volume\n  path. The same type is used when restoring a sandbox so location semantics\n  are not duplicated.\n- `CloudSnapshotOperation` carries the requested kind and tracks capture\n  through `queued`, `in_progress`, `succeeded`, or `failed`; `result` is\n  populated on success.\n- Validation helpers: sandbox-name and hostname rules shared across SDK, CLI, and cloud.\n\nBackend-private materialized state (registry credentials, local cache paths, DB rows, resolved manifest digests, process handles) deliberately stays out of these packages. See each language's README for details.\n\n## Cloud Request Compatibility\n\nCloud request objects ignore unknown fields, including fields inside network and runtime options. This allows newer SDKs to send additive options before the receiving Cloud release understands them. Unknown settings have no effect until that server supports them; request success does not confirm that every supplied setting was applied.\n\nMissing fields retain their documented defaults. Required fields, recognized field types and values, and enum variants still undergo validation. Ignoring unknown object keys does not make new enum variants, renamed fields, or changed field types compatible.\n\nThis policy applies to public Cloud wire types, not host or operator configuration. SDK checks for runtime-only options remain separate from the server's treatment of future Cloud fields.\n","isInternal":false,"tokens":895,"sizeBytes":4181},{"name":"README.md","path":"packages/microsandbox-types/rust/README.md","rawUrl":"https://raw.githubusercontent.com/superradcompany/microsandbox/HEAD/packages/microsandbox-types/rust/README.md","title":"rust Documentation","category":"plugin-manifest","format":"markdown","content":"# microsandbox-types\n\nShared task and wire contract types for microsandbox.\n\nThis crate is the source of truth for the backend-neutral shapes that describe a sandbox and the cloud HTTP bodies that carry them. The Rust SDK, the CLI, and the cloud backend all import these types so they agree on one definition instead of duplicating wire shapes. The generated `@microsandbox/types` TypeScript package is derived from this crate.\n\nIt is a leaf dependency by design. It pulls in `serde`, `serde_json`, `chrono`, `sha2`, and `thiserror`, and nothing from the local VM machinery (no runtime, image, network, or database crates). That keeps it cheap enough for front-end generation, cloud API models, and SDK wrappers to all depend on.\n\n## What This Crate Owns\n\nThe crate models durable user and wire intent: what the user wants to exist, not how a backend fulfills it.\n\n- **Sandbox spec** (`domain` module): `SandboxSpec`, `SandboxResources`, `SandboxRuntimeOptions`, `EnvVar`, `SandboxPolicy`.\n- **Rootfs sources**: `RootfsSource` (bind, OCI, disk image), `OciRootfsSource`, `DiskImageFormat`, `PullPolicy`.\n- **Mounts and patches**: `VolumeMount`, `MountOptions`, `StatVirtualization`, `HostPermissions`, `Patch`, `SecurityProfile`.\n- **Volumes and snapshots**: `VolumeSpec`, `VolumeKind`, `NamedVolumeCreate`, `NamedVolumeMode`, `SnapshotSpec`, and the canonical `SnapshotManifest` schema.\n- **Networking**: `NetworkSpec`, `PublishedPortSpec`, `PortProtocol`.\n- **Exec and logs**: `Rlimit`, `RlimitResource`, `SandboxLogLevel`, `LogSource`, `HandoffInit`.\n- **Cloud wire contracts** (`cloud` module): sandbox, snapshot, pagination, message, and error request/response types.\n- **Validation** (`validation` module): `validate_sandbox_name`, `validate_hostname`, `hostname_from_sandbox_name`, and the `MAX_SANDBOX_NAME_BYTES` / `MAX_HOSTNAME_BYTES` limits.\n\nBackend-private materialized state stays out: registry credentials, local CA paths, replace flags, pull-discovered manifest digests, snapshot upper paths, process handles, and DB rows belong to the SDK and backends, not the contract.\n\n## Usage\n\n```toml\n[dependencies]\nmicrosandbox-types = \"0.7.0\"\n```\n\n```rust\nuse microsandbox_types::{RootfsSource, SandboxResources, SandboxSpec};\n\nlet spec = SandboxSpec {\n    name: \"worker\".into(),\n    image: RootfsSource::oci(\"python\"),\n    resources: SandboxResources { cpus: 2, memory_mib: 1024 },\n    ..Default::default()\n};\n```\n\nThe Rust SDK re-exports the contract types it accepts, so most SDK users get these through `microsandbox::*` and do not depend on this crate directly.\n\n## Serialization Notes\n\n- `SandboxSpec`, `SandboxResources`, `SandboxRuntimeOptions`, `NetworkSpec`, and `MountOptions` use `#[serde(default)]`, so partial JSON fills missing fields from static defaults.\n- Lowercase-on-the-wire enums (`StatVirtualization`, `HostPermissions`, `SecurityProfile`, `SandboxLogLevel`, `LogSource`, `PortProtocol`) match the CLI grammar and the TypeScript string unions.\n- `VolumeMount` has hand-written `Serialize`/`Deserialize`. It tags variants with a `type` field and accepts a legacy top-level `readonly` flag, folding it into `MountOptions` on read.\n- Static defaults only. Nothing here reads process-global or profile config; the SDK and backends apply environment defaults before execution.\n\n## TypeScript Generation\n\nThe `ts` feature derives `ts_rs::TS` on exported wire types and builds the `microsandbox-types-generate` binary, which writes `domain.ts`, `snapshot.ts`, and `cloud.ts` beneath `../typescript/src`.\n\n```bash\n# Regenerate the checked-in bindings.\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate\n\n# Verify they are current (used in CI; exits non-zero when stale).\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate -- --check\n```\n\nA unit test (`checked_in_bindings_match_generated_output`) also fails when any generated TypeScript file drifts, so `cargo test --features ts` catches stale bindings too.\n\n## Testing\n\n```bash\ncargo test -p microsandbox-types\ncargo test -p microsandbox-types --features ts\n```\n","isInternal":false,"tokens":959,"sizeBytes":4095},{"name":"README.md","path":"packages/microsandbox-types/typescript/README.md","rawUrl":"https://raw.githubusercontent.com/superradcompany/microsandbox/HEAD/packages/microsandbox-types/typescript/README.md","title":"typescript Documentation","category":"plugin-manifest","format":"markdown","content":"# @microsandbox/types\n\nShared task and wire contract types for microsandbox, as TypeScript.\n\nThis package is the type-only mirror of the cloud contract in the `microsandbox-types` Rust crate. It gives TypeScript consumers (the cloud front end and anything talking to the cloud API) the exact cloud wire shapes microsandbox uses, so a `CloudSandboxSpec` means the same thing on both sides of the wire. Only the domain types the cloud contract references are generated; the broader SDK/domain surface is not.\n\nIt contains no runtime code. Every export is a type; importing it adds nothing to your bundle.\n\n## Generated, Not Hand-Written\n\nThree files are generated from the Rust crate with [`ts-rs`](https://github.com/Aleph-Alpha/ts-rs), each carrying a `// @generated` header. Do not edit them:\n\n- `src/cloud.ts` — the package entry: the cloud wire types, which import and re-export their domain and snapshot dependencies.\n- `src/domain.ts` — only the domain types the cloud twins transitively reference (`EnvVar`, `Rlimit`, `NetworkPolicy`, …).\n- `src/snapshot.ts` — the canonical snapshot manifest schema embedded by cloud snapshot resources.\n\nTo change a shape, edit the Rust type and regenerate from the repo root:\n\n```bash\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate\n```\n\n## Install\n\n```bash\nnpm install @microsandbox/types\n```\n\n## Usage\n\nImport the shapes you need with `import type`:\n\n```ts\nimport type {\n  CloudCreateSandboxRequest,\n  CloudSnapshot,\n  CloudSnapshotOperation,\n} from \"@microsandbox/types\";\n\nfunction createSandbox(request: CloudCreateSandboxRequest) {\n  // POST the source-tagged request to the cloud API\n}\n```\n\n`CloudCreateSnapshotRequest` and `CloudSnapshot` are unions discriminated by\n`kind`; the current public variant is `\"disk\"`. `CloudSnapshotOperation` carries\nthat same kind and exposes only the public statuses `queued`, `in_progress`,\n`succeeded`, and `failed`. Both snapshot creation results and disk-snapshot\nrestore requests use `CloudSnapshotLocation` to distinguish a managed artifact\nID from a host-volume path.\n\n## Generated Shape Notes\n\nThe bindings follow `ts-rs` conventions, which mirror the Rust serde representation:\n\n- Cloud enums are internally tagged with a `type` field: `CloudRootfsSource` is `{ type: \"bind\"; … } | { type: \"oci\"; reference: string } | { type: \"disk_image\"; … }`, and `CloudVolumeMount` / `CloudHostPattern` / `CloudViolationAction` follow the same shape.\n- `CloudCreateSandboxRequest` is a flat union discriminated by `source: \"oci\" | \"bind\" | \"disk_image\" | \"disk_snapshot\"`. Common sandbox fields stay flat on every variant; source-specific fields such as `reference`, `path`, and `disk_snapshot_ref` appear only where they apply. Servers also accept the legacy `image` request shape without `source` during migration.\n- `CloudCreateSnapshotRequest` and `CloudSnapshot` are flat unions discriminated by the current `kind: \"disk\"`; common snapshot fields stay flat beside the discriminator.\n- Lowercase domain enums like `StatVirtualization` are string-literal unions (`\"strict\" | \"relaxed\" | \"off\"`).\n- Optional Rust fields are `T | null`; fields skipped when absent are `?:` optional.\n- Referenced domain and snapshot types live in `domain.ts` and `snapshot.ts` and are re-exported from the package entry, so a single import from `@microsandbox/types` sees the whole cloud contract.\n\n## Build And Typecheck\n\n```bash\nnpm run build       # tsc -> dist/\nnpm run typecheck   # tsc --noEmit\n```\n","isInternal":false,"tokens":811,"sizeBytes":3512},{"name":"README.md","path":"packages/protocol-client/rust/README.md","rawUrl":"https://raw.githubusercontent.com/superradcompany/microsandbox/HEAD/packages/protocol-client/rust/README.md","title":"rust Documentation","category":"plugin-manifest","format":"markdown","content":"# Generic framed client\n\n`Client<P>` owns one byte connection, its correlation IDs, and all routed subscriptions. `P: Protocol` establishes the transport and selects message metadata and an `EnvelopeCodec`. Protocol consumers can implement both traits outside this crate. `tests/engine.rs` exercises external protocol setup and routing, while `tests/external_codec.rs` supplies an external connector and a non-CBOR envelope codec across native, encoded, raw, checked, stream, explicit-ID, and exact-packet calls. Rust native payload serialization remains CBOR; the custom codec controls its enclosing envelope, and encoded/raw paths retain caller-supplied bytes.\n\n```text\nnative payload ---- TypedMessage ---+\nencoded payload --- EncodedMessage -+--> codec --> shared writer\nraw envelope -----------------------+-----------> shared writer\nexact packet -----------------------------------> shared writer\n\nshared reader --> ID routing --> RawStream --> Stream (decode on recv)\n```\n\n`request` returns the first response. If it is not terminal, the router retains the ID and discards later replies until terminal completion. `request_typed` additionally requires a terminal response and invokes a borrowed `Request<P>` implementation's checked decoder. Application errors remain that implementation's responsibility; the generic engine does not equate a terminal response with success.\n\n`stream` and `stream_raw` return owned subscriptions. `into_parts()` moves them into a cloneable sender and one receiver. Both retain the connection. Dropping or closing the receiver disables sends and drains any admitted operation until terminal completion; it does not send a process signal, file EOF, TCP close, or cancellation packet. Old sender handles cannot gain permission when an ID is reused.\n\n`send` and `send_raw` support explicit IDs owned by the connection. `write_unchecked` serializes caller-owned packet bytes without creating an ID or response subscription. Native and encoded-payload paths apply protocol availability gates; raw paths preserve opaque bytes. A decoded `Message` retains its original `RawFrame`, including unknown envelope fields.\n\nConnections are shared handles. Explicit `close(&self)` closes every handle and wakes waiters. `closed().await` observes closure, including idle peer disconnection, without polling; canceling that wait leaves the connection alone. Last-owner drop closes the transport; the reader and writer do not keep the owner alive. A stream remains an owner after a temporary client handle is dropped.\n\nQueues have item and combined byte limits. A retained slow receiver can backpressure the whole connection; fair per-stream flow control is not provided by the wire protocol. Dropping that receiver releases its queued frames and wakes blocked routing. Active and draining IDs both consume in-flight capacity.\n\n`ClientError.delivery` distinguishes `NotSent` before writer admission from `Unknown` afterward. Timeouts and dropped futures stop local waiting, without retrying or undoing peer work. An EOF before terminal completion is an error; truncation inside a frame is reported separately. Optional incomplete-frame deadlines start at the first byte and do not expire idle connections.\n\nThe `uds` and `named-pipe` features enable `LocalConnector` on their platforms. It uses the endpoint supplied by the caller without inventing names. A custom `Connector` receives an absolute setup deadline and returns an exclusively owned byte stream. `connect_stream` accepts a transport that the caller has already dialed and authenticated.\n\nValidation: `cargo test -p microsandbox-protocol-client --all-features --locked`. The Unix connector test binds a temporary socket and therefore needs an execution environment that permits local socket creation.\n\nProtocols choose whether completed correlation IDs may be reused. Agent connections retire IDs for the lifetime of the connection, matching the relay; control connections may reuse an ID after its terminal response. Exhaustion is a local error and does not reconnect or replay a request.\n","isInternal":false,"tokens":770,"sizeBytes":4073},{"name":"README.md","path":"packages/protocol-client/typescript/README.md","rawUrl":"https://raw.githubusercontent.com/superradcompany/microsandbox/HEAD/packages/protocol-client/typescript/README.md","title":"typescript Documentation","category":"plugin-manifest","format":"markdown","content":"# @microsandbox/protocol-client\n\nA protocol-neutral framed client for TypeScript. `Client<P>` owns one byte transport, correlation IDs, bounded queues, a serialized writer, and response routing. `P` owns setup, its envelope codec, and message gates. Agent and control operations stay in their protocol packages; sandbox lifecycle stays in the SDK.\n\n```text\n                 Client<P>\n          /          |           \\\n native payload   encoded      raw frame / exact packet\n          \\          |           /\n          protocol codec + shared router\n                       |\n                ByteTransport\n```\n\n## Implementing a protocol\n\nAn external package can implement setup and metadata using only public declarations. This example protocol reads one ready byte and uses the shared CBOR envelope. Its server must implement that same handshake.\n\n```ts\nimport {\n  Client, CborEnvelopeCodec, readExactly,\n  type Protocol, type ByteTransport, type EstablishContext, type Established, type SendMetadata,\n} from \"@microsandbox/protocol-client\";\n\nclass ExampleProtocol implements Protocol<number> {\n  async establish(transport: ByteTransport, context: EstablishContext): Promise<Established<number>> {\n    const ready = (await readExactly(transport, 1, context.signal))[0]!;\n    return {\n      transport, ready, codec: new CborEnvelopeCodec(), limits: context.limits,\n      ids: { start: 1, endExclusive: 2 ** 32 },\n    };\n  }\n  prepare(ready: number, _wireName: string): SendMetadata {\n    return { generation: ready, flags: 0 };\n  }\n}\n\nasync function connect(transport: ByteTransport) {\n  return Client.connectTransport(transport, new ExampleProtocol(), { setupTimeoutMs: 10_000 });\n}\n```\n\n`ByteTransport.read(maxBytes)` returns between one and `maxBytes` ordered bytes, or `null` for clean EOF. The engine makes one read at a time and independently serializes writes. `close()` must wake both directions. Passing a transport transfers ownership, including when setup fails. `Connector.connect(context)` supplies repeatable dialing with a deadline and abort signal; it does no discovery itself. `LocalConnector` and `NodeTransport` live at `@microsandbox/protocol-client/node`. Browser-safe `WebSocketConnector` and `WebSocketTransport` are root exports.\n\n`EnvelopeCodec.encodePayload()` supports protocol-specific native encoders. `encode()` wraps supplied payload bytes; `decode()` returns an inspectable `InboundFrame` retaining its `raw` frame. The standard codec uses `{ v, t, p }`, with `p` an actual CBOR byte string, strict unsigned integers, and open message names. Raw subscriptions never invoke that codec.\n\n## Public access levels\n\n| API | Input and result |\n| --- | --- |\n| `request(typedMessage(name, value))` | Native payload; first `InboundFrame` reply |\n| `request(encodedMessage(name, bytes))` | Pre-encoded application payload; first message reply |\n| `requestTyped(request)` | Optional checked request and result decoder; requires terminal reply |\n| `openStream(message)` | Owned native stream through terminal |\n| `requestRaw(flags, body)` / `openStreamRaw(flags, body)` | Opaque envelope bodies and raw replies |\n| `sendOnStream(id, message)` / `sendRaw(id, flags, body)` | Explicit ID owned by this connection |\n| `writeUnchecked(bytes)` | Exact transport bytes without framing or semantic checks |\n| `stream.split()` | Separate owned sender and single receiver |\n| `client.ready` / `clone()` / `close()` | Protocol metadata and shared connection lifetime |\n\nChecked helpers are optional; neither native messages nor raw responses are narrowed to a generated message enum. Application errors remain distinct from `ClientError`. Unknown message names, payload bytes, and envelope fields remain inspectable.\n\n## Limits, ownership, and cancellation\n\nDefault engine limits are 4 MiB per frame, 1024 in-flight exchanges, 256 queued writes, 1024 queued responses per exchange, and 8 MiB of combined engine-owned packet buffering. Protocol setup can select tighter limits. Counts exclude caller-owned inputs, returned frames, and operating-system or browser socket buffers. Native transports read in paused mode; the WebSocket adapter separately caps its input queue and closes on overflow.\n\nA setup deadline covers dialing and protocol establishment. Optional request deadlines cover writer admission and local response waiting; incomplete-frame deadlines start only when a frame begins. Idle connections have no implicit incomplete-frame deadline. `AbortSignal` cancels local waiting. `delivery: \"not_sent\"` means an attempt never entered the writer; `\"unknown\"` means it may have reached the peer. The engine never retries an admitted operation.\n\nClosing a stream only stops local delivery. Nonterminal abandoned exchanges retain their IDs until terminal; stale split senders remain invalid after ID reuse. A receive timeout leaves future frames available. Shared `client.close()` wakes every handle and closes the transport. Close explicitly in `finally`: JavaScript finalization is best-effort, while explicit close is deterministic.\n\n## Development\n\nFrom `packages`, run `npm ci`, `npm run build`, `npm run typecheck`, and `npm test`. Tests exercise an externally defined protocol, full-width IDs, byte preservation, routing, cancellation and admission, ownership, backpressure, transport truncation, WebSocket bounds, and public README examples. Live runtime and historical SDK compatibility tests are additional release requirements.\n\nTo check package exports independently of workspace resolution, install locally packed client packages into a separate consumer directory, then run `node protocol-fixtures/check-packed-protocol-consumer.mjs /path/to/consumer` from `packages`. The consumer lockfile must identify the protocol-client tarball and integrity, and the installed package must not be a workspace symlink. This compiles and executes a custom connector, non-CBOR envelope codec, native/encoded/raw/checked calls, full-width IDs, split streams, explicit sends, and exact packet writes against the installed declarations.\n\nThe command prints a retained output directory. With the packed agent and control packages also installed in that consumer, pass that output directory to `node protocol-fixtures/check-browser-package-roots.mjs /path/to/consumer/protocol-consumer-output`. This bundles all three root exports for the browser, rejects Node builtin imports, and executes the same protocol consumer without Node globals. It verifies browser package loading; actual browser/WebSocket integration remains a separate check.\n\nProtocols choose whether completed correlation IDs may be reused. Agent connections retire IDs for the lifetime of the connection, matching the relay; control connections may reuse an ID after its terminal response. Exhaustion is a local error and does not reconnect or replay a request.\n","isInternal":false,"tokens":1370,"sizeBytes":6843}],"systemPromptSnippet":"<yakaai_skills repo=\"superradcompany/microsandbox\">\n<!-- File: AGENTS.md (Tokens: ~5462 | Category: root-instruction) -->\n# AGENTS.md\n\nThese instructions are only for agents contributing changes to this repository.\nDo not apply them to other repositories or to general agent behavior outside this contribution workflow.\n\n## Scope\n\n- Follow these guidelines when editing code, docs, tests, examples, CI, or release files for microsandbox.\n- Prefer repository conventions over generic agent habits. When unsure, inspect nearby files and match their style.\n- Do not create branches, commit, push, tag, publish, or open pull requests unless the human explicitly asks.\n- Check `git status --short --branch` before making changes. Do not overwrite or revert user work unless explicitly asked.\n\n## Project Map\n\n- `sdk/rust` is the public Rust SDK crate.\n- `crates/cli` contains the `msb` CLI.\n- `crates/runtime` contains VM runtime integration.\n- `crates/filesystem`, `crates/image`, `crates/network`, `crates/db`, `crates/migration`, `crates/metrics`, `crates/metrics-collector`, `crates/protocol`, and `crates/utils` are shared internal crates.\n- `packages/agent-client` and `packages/microsandbox-types` are the shared agent-protocol client and wire-contract type packages, each with Rust and TypeScript implementations.\n- `crates/agentd` is the in-guest agent. It is a workspace member; the musl guest binary that ships in releases is built separately.\n- `sdk/python`, `sdk/node-ts`, and `sdk/go` contain the language SDKs and native bindings.\n- `docs/` contains the documentation site. Keep docs in sync with user-facing behavior.\n- `examples/` contains runnable examples. Add new example projects only when requested or clearly required by the contribution.\n- `mcp/` and `skills/` are submodules related to agent integrations.\n- `vendor/libkrunfw` is a submodule for the kernel firmware library.\n\nRepository layout:\n\n```text\n.\n|-- AGENTS.md\n|-- Cargo.lock\n|-- Cargo.toml\n|-- COMPATIBILITY.md\n|-- DEVELOPMENT.md\n|-- Dockerfile.agentd\n|-- justfile\n|-- msb-entitlements.plist\n|-- assets/\n|-- crates/\n|   |-- agentd/\n|   |-- cli/\n|   |-- db/\n|   |-- filesystem/\n|   |-- image/\n|   |-- metrics/\n|   |-- metrics-collector/\n|   |-- migration/\n|   |-- network/\n|   |-- protocol/\n|   |-- runtime/\n|   |-- testing/\n|   |   |-- init/\n|   |   |-- macros/\n|   |   `-- utils/\n|   `-- utils/\n|-- docs/\n|   |-- changelog/\n|   |-- cli/\n|   |-- getting-started/\n|   |-- images/\n|   |-- networking/\n|   |-- observability/\n|   |-- recipes/\n|   |-- sandboxes/\n|   |-- sdk/\n|   `-- security/\n|-- examples/\n|   |-- python/\n|   |-- rust/\n|   `-- typescript/\n|-- mcp/\n|   |-- bin/\n|   |-- src/\n|   `-- package.json\n|-- packages/\n|   |-- agent-client/\n|   `-- microsandbox-types/\n|-- packaging/\n|   `-- docker/\n|-- scripts/\n|   `-- smoke/\n|-- sdk/\n|   |-- go/\n|   |-- node-ts/\n|   |-- rust/\n|   `-- python/\n|-- skills/\n|   `-- microsandbox/\n`-- vendor/\n    `-- libkrunfw/\n```\n\n## Design Principles\n\n- Before making or continuing a change that may introduce a regression, breaking change, or backward-compatibility risk identified below, stop and alert the human with the likely impact and affected workflows.\n- Keep changes narrowly scoped to the requested behavior. Avoid drive-by refactors, unrelated formatting, or dependency churn.\n- Treat sandbox isolation, host filesystem access, networking, and secret handling as security-sensitive. Validate inputs at boundaries and avoid exposing host paths, credentials, or ambient privileges.\n- For public APIs, keep the Rust SDK, CLI, Python SDK, Node SDK, Go SDK, docs, and examples consistent when they describe the same capability.\n- Prefer explicit errors with useful context over silent fallbacks.\n\n## Host Path Handling\n\n- For every path input, identify whether it belongs to the local host, a remote backend, or the guest before resolving it. Never resolve cloud or guest paths against the SDK client's working directory.\n- Resolve local host paths against their documented base once, before deferred use or persistence. Retained handles and asynchronous operations must reuse that resolved path, including their final writes. Sandbox and SDK configuration-file inputs use the contributing file's directory, including managed SDK settings.\n- Making a path absolute must preserve its symlink policy. Do not substitute filesystem canonicalization or collapse parent components across symlinks without reviewing the behavior change.\n- Preserve existing persisted relative paths and their legacy startup/resource-inheritance behavior. They have no reliable original base unless it was saved: do not reject, migrate, prompt about, or rewrite them during restart. Capture absolute host inputs only for new sandboxes, including new restore/branch children, without modifying their source sandbox.\n- Path changes need regression coverage with different creation and consumption directories, plus missing targets and symlinks where applicable. Change process cwd only inside an isolated test subprocess. Review sandbox mounts, rootfs paths, TLS files, backend storage roots, snapshots, transfers, and generated commands when adding a new path input.\n\n## Backward Compatibility Review\n\nBackward-compatibility detection is a required part of working on this project. Surface potential compatibility breaks before making or continuing the affected change.\n\nThe review's immediate goal is proactive detection and reporting, not automatically implementing compatibility fixes. Do not silently add compatibility layers, migrations, legacy codecs, fallback paths, or downgrade behavior. When a material risk is found, explain the affected releases, components, persisted artifacts, users or workflows, the likely failure mode, and the available options; then wait for human direction as required by the Design Principles above.\n\nSDK-to-`msb` compatibility is required in both directions for future changes:\n\n- Newer SDKs must continue to work with older `msb` binaries for existing supported workflows.\n- Newer `msb` binaries must continue to work with older SDKs for existing supported workflows.\n- Apply this requirement to every language SDK and the complete SDK/runtime interaction, including binary resolution, launch arguments and JSON, inherited descriptors, startup responses, control and agent protocols, and shared persisted state. Compatibility with an already-running agent alone does not establish launch compatibility.\n- A new feature unavailable in an older peer must be detected and produce a clear unsupported-feature or upgrade-required error, or use an explicitly approved fallback. It must not silently lose requested behavior or break unrelated existing functionality.\n- Do not assume matching package versions or bundled binaries satisfy this requirement. Review independently installed runtimes, including those resolved from `MSB_HOME`, and use cross-version tests or historical fixtures for affected boundaries; same-version tests alone are insufficient evidence.\n\nBidirectional SDK/runtime compatibility does not mean freezing the shared catalog at the oldest installed or running version. Newer SDKs and CLIs may apply validated catalog upgrades while older VM runtimes remain running. Do not add a blanket \"stop all sandboxes before upgrading\" gate or preserve an old catalog solely because an older runtime is present. Distinguish an older VM process continuing its database writes from an older SDK/CLI reopening the catalog and running its own schema-admission checks; test and report these separately rather than treating one as evidence for the other.\n\nFor online catalog upgrades, preserve migration serialization, transaction safety, recovery journals, and active maintenance leases. Evaluate the actual SQL and persisted-data contracts used by older runtimes; neither an additive-looking migration nor a version difference alone proves safety or incompatibility. Live tests must exercise an older VM across the upgrade, verify retained execution and data, and cover new-SDK launch, control, restart, and cleanup through older runtimes. If a concrete incompatible migration is found, report that specific conflict for a decision rather than reintroducing a blanket compatibility blocker.\n\nIf a proposed change may violate either direction, flag it before implementation and wait for human direction; this requirement does not authorize silently building adapters or choosing a breaking change. Any exception or change to the supported compatibility horizon requires explicit human direction. The known v0.6.9 ↔ v0.6.10 launch incompatibility is an accepted historical exception and must not be repaired as part of unrelated work. It does not exempt future changes from this review or requirement.\n\nBy explicit user direction on 2026-09-13, the historical SDK/runtime compatibility target for this work is v0.6.x, with v0.6.0 as the floor, against the current implementation candidate in both directions. Releases older than v0.6.0, including all v0.4.x and v0.5.x releases, are excluded from required compatibility. Preserve their historical results as diagnostic evidence. This supersedes the earlier exact-v0.5.0 exclusion. Existing codecs and capability gates are not removed or changed by this scope decision. The known v0.6.9 ↔ v0.6.10 exception remains unchanged; other v0.6.x failures are not waived.\n\nBefore changing an existing cross-version boundary, determine:\n\n- What older component, binary, sandbox, or persisted state may interact with the change.\n- Which side is the old producer or consumer.\n- Whether failure would be a clean refusal, loss of functionality, stranded state, silent misinterpretation, data loss, or possible corruption.\n- Whether the repository has a fixture or test using an actual older artifact or binary, rather than only same-version round trips.\n\nAlways perform this review when a change affects any of these areas:\n\n- Host-to-guest framing, message names, flags, IDs, payload fields, version negotiation, bootstrap, or lifecycle handshakes.\n- Unix sockets, Windows named pipes, path hashing, relay routing, control messages, inherited file descriptors, signals, or process-launch JSON.\n- SQLite schemas, migration history, persisted configuration, downgrade behavior, journals, leases, or runtime recovery state.\n- ext4, EROFS, VMDK, OCI layers, filesystem metadata, device IDs, mount tags, block paths, or other on-disk formats.\n- Snapshots, archives, manifests, canonical serialization, digests, parent identities, filenames, or atomic publication order.\n- Cache keys, materializer ABIs, layer ordering, whiteouts, hardlinks, xattrs, or content-addressed storage.\n- The runtime, agentd, libkrun, firmware, kernel patches, virtio devices, shared-memory layouts, or package-version coupling.\n- Network address derivation, MACs, interface names, DNS aliases, published ports, TCP/UDP behavior, vsock, SSH, or SFTP behavior.\n- Heartbeats, boot errors, metrics, logs, lock files, runtime directories, or other files consumed across process or release boundaries.\n\nCheck each applicable compatibility direction:\n\n1. A new host interacting with an old running sandbox and old agentd.\n2. A new release opening an existing `MSB_HOME` and its database.\n3. A new release reading old disks, snapshots, archives, caches, and filesystem metadata.\n4. An older release encountering state written by the new release, including downgrade refusal behavior.\n5. Exported artifacts moving between releases, platforms, or architectures.\n6. Independently running components from different releases communicating during an upgrade.\n7. A newer SDK launching and operating an older `msb` binary.\n8. An older SDK launching and operating a newer `msb` binary.\n\nTreat stable strings, numeric constants, paths, hashes, serialized field details, ordering guarantees, timing, and error interpretations as compatibility-sensitive even when they are not part of the public API.\n\nFor changes affecting persisted data or cross-version communication, read [COMPATIBILITY.md](COMPATIBILITY.md) before implementation. Follow its contract-version naming, module ownership, migration, and validation guidelines.\n\n## Path Handling\n\n- Every relative path must have an explicit base directory. Never rely on the working directory at the time of eventual use.\n- Resolve CLI paths against the invocation directory, config-file paths against the config file’s directory, and SDK paths against a documented base.\n- Capture that base and resolve host paths before spawning asynchronous work or passing them to another process.\n- Persist absolute host paths when they reference a fixed local resource that must remain the same across restarts.\n- Distinguish host paths, guest paths, volume-relative paths, and resource identifiers. Do not apply host-path normalization to all strings.\n- Making a path absolute, collapsing `..`, and resolving symlinks are different operations. Choose deliberately; they can select different destinations.\n- Use structured path fields internally. Avoid concatenating paths into delimiter-separated strings that become ambiguous with valid filenames.\n- Return explicit errors when resolution fails. Never silently substitute another directory or panic.\n- Enforce filesystem containment during the operation, accounting for symlinks and concurrent changes. String-prefix checks alone are insufficient.\n- Centralize path-resolution rules so CLI, SDK, and background execution cannot drift.\n\nFor path-related changes, test creation in directory A followed by use from directory B, plus relevant symlink, `..`, missing-path, and platform-specific cases. Assert the exact file or directory accessed—not just whether the operation succeeded.\n\n## Rust Layout And Style\n\n- Most Rust crates use `lib/lib.rs` for library code and `bin/main.rs` for binaries. Keep using those paths for new crate entries unless the surrounding crate already does something different.\n- When adding a new library or binary target, declare the path explicitly in `Cargo.toml`:\n\n```toml\n[lib]\npath = \"lib/lib.rs\"\n\n[[bin]]\nname = \"example\"\npath = \"bin/main.rs\"\n```\n\n- Keep crate roots and module roots thin. `lib.rs` and `mod.rs` should declare modules, crate attributes, and exports only. Put implementation in leaf modules such as `sandbox/config.rs`, `policy/types.rs`, or `commands/run.rs`.\n- File order should be:\n  1. Module docs and crate/file attributes, such as `//! ...` and `#![warn(missing_docs)]`.\n  2. `use` imports.\n  3. Sectioned items.\n- Group imports by origin, separated by blank lines: standard library first, external crates second, then `crate::` and `super::` imports.\n- Do not put `use` statements inside sections unless there is a narrow local reason, such as a test module import.\n- Strongly prefer importing types at the top of the file or test module over spelling out full module paths in signatures, patterns, and function bodies. Use an import alias for name collisions; keep qualified paths when they meaningfully clarify an otherwise ambiguous name.\n- Use the exact section delimiter shown below. Do not invent alternate Markdown-style, shorter, or decorative section headers.\n- Include only sections that contain items. Do not add empty sections just to satisfy the full order.\n- Organize Rust files with these section headers, in this order when applicable:\n\n```rust\n//--------------------------------------------------------------------------------------------------\n// Constants\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Types\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Methods\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Trait Implementations\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Functions\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Macros\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Tests\n//--------------------------------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------------------------------\n// Re-Exports\n//--------------------------------------------------------------------------------------------------\n```\n\n- Aggregator files that only expose modules and public items may use `Exports` instead of `Re-Exports` when matching existing files.\n- Use qualified section labels only to split large sections into obvious groups, for example `Types: Identifiers`, `Functions: Handlers`, or `Functions: Helpers`.\n- Do not create a qualified section for one or two items unless the surrounding file already uses that pattern.\n- Put constants and statics under `Constants`.\n- Put `struct`, `enum`, `trait`, and `type` definitions under `Types`.\n- Put inherent `impl Type` blocks under `Methods`, directly after the related type definitions when practical.\n- Put `impl Trait for Type` blocks under `Trait Implementations`.\n- Put free functions under `Functions`. If a free function is only used by one public function, place it later in `Functions: Helpers`.\n- Put macros under `Macros`, not near the call site.\n- Put unit tests under `Tests`, usually as `#[cfg(test)] mod tests`. Keep test-only helpers in the same section.\n- Put public re-exports under `Re-Exports`, or `Exports` in root files that use the existing aggregator style.\n- Keep items in dependency order inside a section: public surface first, private helpers later.\n- Keep docs on public types, fields, methods, functions, and modules. This repo uses `#![warn(missing_docs)]` in public crates, so new public items should explain what they are for.\n- Prefer explicit domain types over loosely typed strings, booleans, or tuples when the value crosses an API or subsystem boundary.\n- Separate logical steps with blank lines: setup, early returns, normalization, validation, operations, and final results. Apply the same spacing to test setup, actions, and assertions; do not rely on `cargo fmt` to provide it.\n- Prefer named intermediate values when an expression combines distinct steps, especially fallible lookups, parsing, and validation. Make each failure point easy to identify.\n- Keep iterator chains simple. Prefer a straightforward loop when nested closures or branching make the logic harder to follow; do not add helpers solely to hide that complexity.\n- Keep straightforward logic together. Extract helpers when they clarify a distinct responsibility or remove meaningful duplication, rather than merely shortening a function.\n- Write comments to explain intent, constraints, or non-obvious behavior rather than narrating the code.\n- Name values for what they currently represent. Avoid names that imply an action has already happened when the value describes a requirement or planned action.\n- During refactors, conflict resolution, bug fixes, and feature work, call out any expected behavior, API, or data-format changes and wait for direction when the risk is material.\n- Use `thiserror` or existing local error patterns for typed errors. Include enough context for callers to understand the failing operation.\n- Keep shared library and SDK errors interface-neutral: describe the problem and remedy without CLI flags or command syntax. Add command-specific guidance in the CLI layer.\n- In async code, avoid holding locks across `.await`. Prefer explicit ownership, short critical sections, and existing Tokio patterns in the surrounding module.\n- Keep feature-gated code close to the feature it gates and use existing `#[cfg(feature = \"...\")]` patterns.\n- Do not add examples under `examples/` unless requested or clearly required. Prefer tests and docs for small usage coverage.\n- Run `cargo fmt` before finalizing Rust changes.\n\n## Development Build Notes\n\n- If you build `msb` to run it locally on macOS, make sure the binary is codesigned with `msb-entitlements.plist`; otherwise VM/runtime failures may be caused by missing entitlements instead of your code change.\n- Prefer `just build` or `just build-msb` when producing a runnable local binary. The macOS recipe rebuilds `msb` and runs:\n\n```bash\ncodesign --entitlements msb-entitlements.plist --force -s - build/msb\n```\n\n- If you bypass `just` and call `cargo build` directly, manually codesign the exact `msb` binary you are going to run before testing sandbox startup, protocol, networking, or filesystem behavior.\n\n## Validation\n\nUse focused checks for the files you touched, then broader checks when the change crosses crate, SDK, CLI, or runtime boundaries.\n\nCommon Rust checks:\n\n```bash\ncargo fmt --all -- --check\ncargo clippy --workspace -- -D warnings\ncargo test --workspace\ncargo build -p microsandbox-cli\n```\n\n`agentd` is a workspace member, so the workspace-wide commands above cover it. The musl guest binary that ships in releases is built separately via `just build-agentd`.\n\nPython SDK checks:\n\n```bash\ncd sdk/python\nuv sync --group dev\nuv run maturin develop --release\nuv run pytest\nuv run ruff check .\n```\n\nNode SDK checks:\n\n```bash\ncd sdk/node-ts\nnpm ci\nnpm run build\nnpm test\nnpm run typecheck\n```\n\nGo SDK checks:\n\n```bash\ncd sdk/go\ngo test -count=1 .\ngo test -tags \"smoke microsandbox_ffi_path\" -count=1 -timeout 2m .\n```\n\nIntegration tests may require Linux with KVM or macOS Apple Silicon support. If a needed check cannot run in the current environment, say exactly which command was skipped and why.\n\nThe full local setup and build loop is documented in `DEVELOPMENT.md`. Use `just setup`, `just build`, and `just install` when you need the full local runtime, `agentd`, or `libkrunfw` artifacts.\n\n## Commits\n\n- Use Conventional Commits for commit titles: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `ci`, or `build`.\n- Use a scope when it clarifies the affected area, for example `fix(network): ...` or `docs(sdk): ...`.\n- Keep the subject imperative, lowercase after the colon, no trailing period, and at most 72 characters.\n- Include a commit body for non-trivial changes. Explain what changed and why.\n- Use signed commits: `git commit -S`.\n- Before committing, inspect the actual diff, including new, modified, and deleted files. Do not write a commit message from filenames or previous commit messages alone.\n- If there is nothing to commit, say so rather than creating an empty commit.\n\nExample:\n\n```text\nfix(network): wake smoltcp after accepted host connections\n\nNotify the network loop after accepting a published-port connection so\npending guest traffic can make progress without waiting for another timer.\n```\n\n## Branches And Pull Requests\n\n- You may make local edits while on `main`, but do not commit directly to `main`. Start from the latest `main` when creating a contribution branch.\n- Use short, descriptive, kebab-case branch names. Avoid personal prefixes in shared documentation unless the maintainer asks for one.\n- Before opening a PR, compare against the intended base branch and inspect the actual diff:\n\n```bash\ngit log origin/main..HEAD --oneline\ngit diff origin/main..HEAD --stat\ngit diff origin/main..HEAD --name-status\n```\n\n- PR titles should follow Conventional Commit style and stay under 72 characters.\n- PR descriptions should be plain and accurate:\n  - `## TL;DR`: one or two short sentences.\n  - `## Description`: a flat bullet list of core changes.\n  - `## Test Plan`: concrete commands or observable checks.\n- Do not use emojis in PR titles or descriptions.\n- If a PR description includes an API example, verify every symbol, path, flag, type, field, and signature against the diff before writing it.\n\n## Version And Release Changes\n\n- Do not bump versions, publish packages, create release tags, or modify release automation unless explicitly asked.\n- All released packages share a version. When a version bump is requested, check `Cargo.toml`, `sdk/node-ts/package.json`, `mcp/package.json`, and any other package metadata touched by the release process in `DEVELOPMENT.md`.\n- For release or version PRs, summarize the user-visible changes since the previous version bump and run the relevant dry-run publish checks when practical.\n\n## Documentation And Examples\n\n- For changes under `docs/`, read [the Microsandbox docs skill](.agents/skills/microsandbox-docs/SKILL.md) for page structure, examples, version labels, and validation.\n- Update docs when behavior, configuration, CLI flags, SDK APIs, or examples change.\n- Keep SDK reference introductions to a short purpose statement and a link to the relevant usage guide, then go straight into API entries. Follow the concise structure of the execution and networking reference pages across all SDK languages.\n- Start reference type sections directly with their members when the type heading is sufficient. Avoid redundant “Built by” / “Used by” rows and introductory text that repeats the member descriptions.\n- Do not prepend feature walkthroughs, lifecycle summaries, implementation details, or long examples to reference pages or type sections. Put API-specific details beside the relevant member and broader explanations in the usage guides; avoid duplicating those guides in the reference.\n- In SDK reference pages, wrap usage examples in a collapsed `<Accordion title=\"Example\">`. Keep API signatures and type definitions visible outside the accordion.\n- When removing reference overviews, preserve unique signatures, constraints, result fields, and resource-cleanup requirements beside the relevant member or in the linked usage guide.\n- Omit field lists that only repeat documented builder options. Keep field references for returned data and properties not covered by the builder.\n- Do not use tables in SDK references. Use individual method and property headings with visible signatures and the existing parameter/return blocks. Group returned hash or record fields in the existing field blocks, including types, descriptions, and defaults where needed. Preserve links and existing anchors, and avoid repeating the same description above and inside a return block.\n- In Rust documentation examples, import types at the top of each snippet and use their short names in the body instead of fully qualified paths. Group standard-library imports before external-crate imports.\n- Keep examples realistic and runnable. Do not invent APIs or flags.\n- Prefer editing existing examples over adding new example projects unless the new example is requested or clearly fills a missing user workflow.\n- Documentation should describe current behavior, not future plans, unless the page is explicitly about roadmap work.\n\n## Agent Operating Rules\n\n- Use `rg` or `rg --files` for repository searches.\n- Read the relevant files before editing. Let existing module boundaries guide the change.\n- Make the smallest coherent change that satisfies the request.\n- Avoid destructive git commands such as `git reset --hard` and `git checkout --` unless explicitly requested.\n- Do not edit generated artifacts, lockfiles, or submodule pointers unless the change requires it.\n- If generated files or lockfiles must change, explain why in the final summary.\n- Report what changed, what validation ran, and any checks that were skipped.\n\n<!-- File: .agents/skills/microsandbox-docs/SKILL.md (Tokens: ~447 | Category: anthropic-skill) -->\n---\nname: microsandbox-docs\ndescription: Edit Microsandbox product, CLI, and SDK documentation with verified behavior, concise examples, and the repository's Mintlify conventions. Use for changes under microsandbox/docs.\n---\n\n# Microsandbox docs\n\nFollow the repository's `AGENTS.md` for contribution rules and SDK reference structure. Read the affected pages and check the working tree before editing. If a target branch is specified, start from its latest commit and preserve unrelated branch changes.\n\n## Verify the feature\n\n- Check implementation, configuration schema, CLI flags, and supported SDKs before naming an option or writing an example. Distinguish per-secret settings from sandbox-wide defaults and secret-policy denials from ordinary network denials.\n- Check the published tag or release before attaching a version pill. A merged change is not evidence that a tagged version contains it. State local-only or cloud limitations only where they affect the documented workflow.\n- Make examples internally complete. When an example expects a JSON HTTP denial response, show the response enablement, JSON format, and applicable message setting together, even if some are current defaults.\n\n## Write the page\n\n- Lead with what the reader can do and the shortest useful example. Put the main code sample on the page; use accordions for limits and secondary details.\n- Use short headings that name the operation. Keep violation actions, host permissions such as placeholder passthrough, and response-message configuration conceptually separate.\n- Use `CodeGroup` for supported language examples when a workflow spans SDKs. Show a CLI example only when an equivalent flag or command exists. Put `sandbox.yml` in a YAML configuration section instead of presenting it as a CLI command.\n- Keep prose independent of language-specific syntax when possible. Put language or CLI details beside their example or in the relevant reference page. Preserve meaningful edge cases without repeating them across pages.\n\n## Validate and deliver\n\n- Run `python3 scripts/check-docs-language-order.py` and `git diff --check`. Parse changed YAML examples, check links and anchors, and load the affected pages in the Mintlify preview when available.\n- Report the changed pages, checks, and any behavior or release ambiguity. Follow the user's requested target branch and publishing route.\n\n<!-- File: packages/agent-client/README.md (Tokens: ~412 | Category: plugin-manifest) -->\n# microsandbox agent client\n\nTransport-agnostic clients for speaking the microsandbox agent protocol.\n\nThis layer sits between the protocol package and the high-level SDKs:\n\n```text\nsdk -> agent-client -> protocol-client -> protocol\n```\n\nThe agent package supplies connection handshakes, its existing message codec, and protocol-generation gates. Its public client is a specialization of the shared generic router, which owns IDs, framing, transport lifetime, and request/stream routing. SDK packages own sandbox lifecycle, name resolution, image management, volumes, and metrics.\n\n## Layout\n\n```text\npackages/agent-client/\n├── rust/\n└── typescript/\n```\n\n- `rust/` publishes as `microsandbox-agent-client`.\n- `typescript/` publishes as `@microsandbox/agent-client`.\n\nThe Rust crate has no default transport feature. Local SDK connections opt into `uds` on Unix or `named-pipe` on Windows. An owned `AsyncRead + AsyncWrite` transport can also be supplied, including a caller-authenticated WebSocket adapted to bytes.\n\nThe TypeScript package exposes a browser-safe default entry and keeps Node-only Unix sockets behind `@microsandbox/agent-client/node`; browser/front-end callers use `WebSocketTransport`.\n\n## Message Model\n\nThe public API names protocol boundaries by structure:\n\n- `TypedMessage`: message type plus a native payload object.\n- `EncodedMessage`: message type plus already-CBOR-encoded payload bytes.\n- `RawFrame`: routing fields and an opaque envelope body.\n- `Message` (Rust) / `InboundFrame` (TypeScript): an inspectable envelope retaining its original raw frame.\n- `TransportPacket`: exact transport bytes, including length prefix.\n\nUnchecked packet writes are an explicit escape hatch for relays, tests, and specialized tooling.\n\nRust and TypeScript preserve native, encoded, raw, exact-packet, explicit-ID, and owned split-stream access. See each package README for the API migration and validated examples. Shared TypeScript packages build together through the private npm workspace in `packages`.\n\n<!-- File: packages/agent-client/rust/README.md (Tokens: ~1771 | Category: plugin-manifest) -->\n# microsandbox-agent-client\n\n`AgentClient` is `Client<AgentProtocol>` from `microsandbox-protocol-client`. The agent package supplies relay setup and generation metadata; the shared engine supplies one reader, writer, allocator, and ownership registry. SDK services such as execution collection and filesystem handles remain in `microsandbox`.\n\n```text\nTypedMessage / EncodedMessage / raw envelope / exact packet\n                           |\n                 Client<AgentProtocol>\n                           |\n          current or legacy relay handshake\n                           |\n                       agent relay\n```\n\n## Native, encoded, and raw messages\n\n```rust,no_run\nuse microsandbox_agent_client::{AgentClient, TypedMessage, EncodedMessage};\nuse microsandbox_protocol::{fs::{FsOp, FsRequest, FsResponse}, message::MessageType};\n\nasync fn inspect(client: &AgentClient) -> Result<(), Box<dyn std::error::Error>> {\n    let request = FsRequest {\n        bulk: None,\n        op: FsOp::Stat { path: \"/etc/os-release\".into(), follow_symlink: true },\n    };\n    let reply = client.request(TypedMessage::new(MessageType::FsRequest, &request)).await?;\n    let response: FsResponse = reply.payload()?;\n    println!(\"{}\", response.ok);\n\n    // The caller's payload bytes are not parsed or normalized by this path.\n    let payload = vec![0xa0];\n    let _reply = client.request(EncodedMessage::new(MessageType::Ping, payload)).await?;\n    Ok(())\n}\n```\n\n`Message.t` is the actual wire string. Unknown names and unknown envelope fields remain inspectable: `Message::raw()` borrows the original frame and `into_raw()` returns it. Ordinary requests surface peer error messages as messages. Optional `Request<AgentProtocol>` implementations add checked unary decoding without narrowing the generic API.\n\n## Owned streams and explicit IDs\n\n```rust,no_run\nuse microsandbox_agent_client::{AgentClient, TypedMessage};\nuse microsandbox_protocol::{exec::{ExecRequest, ExecStdin}, message::MessageType};\n\nasync fn exchange(client: &AgentClient, request: &ExecRequest) -> Result<(), Box<dyn std::error::Error>> {\n    let stream = client.stream(TypedMessage::new(MessageType::ExecRequest, request)).await?;\n    let id = stream.id();\n    let (sender, mut receiver) = stream.into_parts();\n    client.send(id, TypedMessage::new(MessageType::ExecStdin, ExecStdin { data: vec![] })).await?;\n    sender.send(TypedMessage::new(MessageType::ExecStdin, ExecStdin { data: b\"input\".to_vec() })).await?;\n    while let Some(message) = receiver.recv().await? {\n        println!(\"{}\", message.t);\n    }\n    Ok(())\n}\n```\n\nBoth split parts retain the connection and ID lease; sender handles may be cloned and there is one consuming receiver. Terminal receipt disables sends and delivers the terminal frame once. Closing or dropping the receiver disables sends and retains bounded drain state until terminal completion. It sends no process signal or filesystem EOF. Transport loss before a terminal frame is an error, distinct from clean exhaustion.\n\n`request_raw(flags, body)`, `stream_raw(flags, body)`, and `send_raw(id, flags, body)` exchange opaque envelopes without CBOR decoding. `write_unchecked(packet_bytes)` serializes exact caller bytes without allocating an ID or subscribing for replies. `TransportPacket` remains an optional standalone framing helper; `packet.into_bytes()` feeds the exact write API. Standalone packet readers require exclusive transport ownership.\n\n## Setup and metadata\n\nEnable `uds` for native Unix sockets or `named-pipe` for Windows pipes. `connect_stream` accepts an owned `AsyncRead + AsyncWrite + Unpin + Send` transport, including `UdsTransport` and `NamedPipeTransport`. The `stream` feature name remains accepted for existing manifests; generic owned transports are available without additional dependencies.\n\n```rust,no_run\nuse microsandbox_agent_client::AgentClient;\nuse std::time::Duration;\n\nasync fn connect(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {\n    let client = AgentClient::connect_with(path, |o| o.setup_timeout(Duration::from_secs(5))).await?;\n    println!(\"{}\", client.ready().agent_version());\n    println!(\"{}\", client.ready().negotiated_version);\n    println!(\"{} ready bytes\", client.ready().ready_bytes().len());\n    client.close().await;\n    Ok(())\n}\n```\n\nSetup has one deadline across dial and handshake. The current `[id_min,id_max]` relay prologue and the supported pre-0.5 `[id_offset]` prologue retain their existing parsing, ID ranges, and ready payload behavior. Known operations use the smaller host/peer generation as their availability gate. Current connections continue emitting the host's existing envelope generation; legacy connections emit generation one. Source-level API changes do not retire the legacy wire path.\n\n## Migration from the previous active client\n\n| Previous API | Shared-client API |\n| --- | --- |\n| `connect(path)` / `connect_stream(stream)` | Same entry points; owned streams no longer require the `stream` feature |\n| `request(type, &payload)` | `request(TypedMessage::new(type, &payload))` |\n| `stream(type, &payload) -> (id, receiver)` | `stream(TypedMessage::new(type, &payload))`, then `id()` / `into_parts()` |\n| `send(id, type, &payload)` | `send(id, TypedMessage::new(type, &payload))` |\n| `request_raw(flags, body)` | Same call; retains drain state after a nonterminal first reply |\n| `stream_raw(flags, body) -> (id, receiver)` | Owned raw stream, then `id()` / `into_parts()` |\n| `send_raw(id, flags, body)` | Same call, requiring a live owned ID |\n| `connect_with_timeout(path, duration)` | `connect_with(path, |o| o.setup_timeout(duration))` |\n| `connect_stream_with_timeout(stream, duration)` | `connect_stream_with(stream, |o| o.setup_timeout(duration))` |\n| `connect_with_deadline(path, deadline)` | `connect_with(path, |o| o.setup_timeout(deadline.saturating_duration_since(Instant::now())))` |\n| `connect_stream_with_deadline(stream, deadline)` | `connect_stream_with(stream, |o| o.setup_timeout(deadline.saturating_duration_since(Instant::now())))` |\n| `ready()` | `ready().agent` |\n| `ready_bytes()` | `ready().ready_bytes()` |\n| `negotiated_version()` / `supports(type)` | `ready().negotiated_version` / `ready().supports(type)` |\n| `protocol()` / `is_legacy_protocol()` | `ready().wire_format` / `ready().is_legacy_protocol()` |\n| `agent_version()` | `ready().agent_version()` |\n| `ensure_version_compat(type)` | `AgentProtocol::ensure_version_compat_for(type, client.ready().negotiated_version)` |\n| `AgentClient::ensure_version_compat_for(type, generation)` | `AgentProtocol::ensure_version_compat_for(type, generation)` |\n| Unwired `AgentStream<T>` / packet-only `AgentTransport` | Active `AgentStream` / owned byte transports accepted directly by `connect_stream` |\n| Connection close by ownership transfer | Shared `close(&self)`; last client/stream owner also closes the transport |\n\n`ClientError.delivery` distinguishes `NotSent` from `Unknown` after writer admission. Requests are never automatically replayed. Raw stream access, known-ID sends, custom transports, ready bytes, dynamic names, and encoded payloads remain available without adopting SDK domain objects.\n\nDeadline conversions use `tokio::time::Instant`, matching the original absolute-deadline API. Keep the conversion inside the options closure so it uses the remaining duration when setup begins. Known native and encoded sends perform the same availability check automatically; the public `AgentProtocol` helper supports callers retaining only the generation. Unsupported operations now use the shared `ClientError` category rather than the previous agent-specific error fields.\n\nValidation: `cargo test -p microsandbox-agent-client --all-features --locked`. Unix-socket tests require permission to bind temporary local sockets. The migrated historical handshake cases are source-level regression tests; they do not replace live historical runtime/SDK validation.\n\nThe SDK retains an internal optimized client for generation-eight bulk transfers and Unix shared arenas. Its existing bulk APIs remain available; the generic client is the public transport-independent framed API.\n\n<!-- File: packages/agent-client/typescript/README.md (Tokens: ~1474 | Category: plugin-manifest) -->\n# @microsandbox/agent-client\n\nLow-level TypeScript access to the microsandbox agent protocol. `AgentClient` is `Client<AgentProtocol>` from `@microsandbox/protocol-client`; the agent package supplies relay setup, its existing encoder, and generation gates. The shared engine owns framing, IDs, queues, cancellation, and transport lifetime. Sandbox lifecycle and process/filesystem convenience APIs belong to the SDK.\n\n```text\nAgentProtocol (setup, encoder, gates)\n                 |\n          Client<AgentProtocol>\n            /          \\\n native / encoded     raw / exact packets\n                 |\n       Unix socket / named pipe / WebSocket\n```\n\n## Installation and entry points\n\nInstall with `npm install @microsandbox/agent-client`. Node.js 22 or newer is required for native transports. The root entry is browser-safe. `@microsandbox/agent-client/node` imports Node's `net` module and accepts native endpoint paths verbatim, including Windows named-pipe names.\n\n## Native messages\n\n```ts\nimport { connectUnix } from \"@microsandbox/agent-client/node\";\nimport { typedMessage } from \"@microsandbox/agent-client\";\n\nconst client = await connectUnix(\"/tmp/msb-agent.sock\", { setupTimeoutMs: 10_000 });\ntry {\n  const response = await client.request(typedMessage(\"core.fs.request\", {\n    op: { Stat: { path: \"/etc/os-release\", follow_symlink: true } },\n  }), { requestTimeoutMs: 5_000 });\n  console.log(response.type, response.decodePayload());\n  console.log(client.ready.agentVersion, client.ready.negotiatedVersion);\n} finally {\n  await client.close();\n}\n```\n\n`typedMessage(name, value)` asks the agent codec to encode a native payload. `encodedMessage(name, bytes)` supplies only the application payload; it does not supply an envelope or framed packet. Both paths apply known message gates and flags. Names are open strings, so extensions remain accessible without editing an enum. These methods do not validate an application schema. `requestTyped()` accepts an optional checked request with its own result decoder and requires a terminal response.\n\nThe current TypeScript agent encoder continues to emit generation-five envelopes, including its existing CBOR map and byte-string representations. The ready generation controls feature gates. The supported pre-0.5 relay prologue selects generation-one envelopes and rejects unavailable filesystem/TCP operations before sending. The captured ready frame and unknown fields remain available through `client.ready.frame` and `client.ready.readyBytes`.\n\n## Browser streams\n\n```ts\nimport { AgentClient, WebSocketConnector, typedMessage } from \"@microsandbox/agent-client\";\n\nconst client = await AgentClient.connectConnector(\n  new WebSocketConnector(\"wss://relay.example.com/agent\"),\n  { setupTimeoutMs: 10_000 },\n);\ntry {\n  const stream = await client.openStream(typedMessage(\"core.exec.request\", {\n    cmd: \"sh\", args: [\"-lc\", \"echo hello\"],\n  }));\n  try {\n    for await (const frame of stream) {\n      console.log(frame.type, frame.payload);\n    }\n  } finally {\n    stream.close();\n  }\n} finally {\n  await client.close();\n}\n```\n\nThe relay must forward the agent's binary byte stream, including its prologue. `WebSocketTransport` also wraps a caller-constructed socket for custom authentication. Its incoming queue defaults to 8 MiB and 4096 messages; overflow closes the connection because browser WebSockets cannot pause incoming messages. Outgoing writes wait for the browser's send queue to drain. The connector and handshake share one setup deadline.\n\n## Raw and exact access\n\n```ts\nimport { type AgentClient, TransportPacket } from \"@microsandbox/agent-client\";\n\nasync function exchangeOpaque(client: AgentClient, envelope: Uint8Array) {\n  const stream = await client.openStreamRaw(0, envelope);\n  const { sender, receiver } = stream.split();\n  try {\n    await sender.send(0, envelope);\n    await client.sendRaw(sender.id, 0, envelope);\n    return await receiver.next({ requestTimeoutMs: 5_000 });\n  } finally {\n    receiver.close();\n    sender.close();\n  }\n}\n\nasync function forwardExactPacket(client: AgentClient, bytes: Uint8Array) {\n  await client.writeUnchecked(TransportPacket.fromBytes(bytes));\n}\n```\n\nRaw APIs leave envelope and payload bytes opaque. `requestRaw(flags, body)` returns the first raw reply. `openStreamRaw()` receives through the terminal reply. `sendRaw(id, flags, body)` requires an active ID owned by this connection. A split sender retains its original lease and cannot regain permission if the numeric ID is reused. `writeUnchecked(bytes)` can write even deliberately malformed transport bytes; `TransportPacket.fromBytes()` optionally validates a single packet first.\n\nNative replies retain the complete original raw frame as `frame.raw`, including unknown envelope fields. `core.error` is an ordinary terminal agent response; interpreting it belongs to the caller or a checked request decoder.\n\n## Ownership and failures\n\nStreams own their send and receive halves. `split()` transfers those halves and invalidates the original stream's send/receive methods. Closing a receiver or leaving async iteration stops local delivery; it sends no remote cancel, signal, or EOF. An abandoned ID remains draining until its terminal frame arrives. A timed-out `next()` does not consume the next arriving frame.\n\n`client.clone()` shares the connection. `client.close()` closes every shared handle and wakes pending operations. Explicitly close clients and streams: JavaScript garbage collection only offers best-effort cleanup. A request deadline covers writer admission and the local response wait; it does not cancel remote execution. `ClientError.delivery` is `not_sent` before admission and `unknown` afterward. Nothing automatically retries an admitted operation.\n\n## Migration from the previous package API\n\n| Previous surface | Shared-engine surface |\n| --- | --- |\n| Independent `AgentClient` class/router | `AgentClient = Client<AgentProtocol>` plus convenience constructors |\n| `handshakeTimeoutMs` | `setupTimeoutMs`, covering dial and handshake together |\n| `client.negotiatedVersion()` | `client.ready.negotiatedVersion` |\n| Closed `MessageType` parameter | Open string names; `MessageType` still lists known names |\n| Packet-oriented custom `AgentTransport` | Ordered `read(maxBytes)`, `write(bytes)`, `close()` byte transport |\n| `InboundFrame.fromRawFrame(frame)` | `new AgentEnvelopeCodec().decode(frame)` |\n| `new AgentStream(...)` | Obtain owned streams from `client.openStream()` or `openStreamRaw()` |\n| Request, encoded payload, stream, exact packet methods | Retained on the generic client, with additional raw and split interfaces |\n\nThese are public source API changes. Existing agent wire behavior is preserved; source and byte tests do not replace the required historical runtime/SDK launch tests.\n\n## Development\n\nFrom the repository's `packages` directory, run `npm ci`, `npm run build`, `npm run typecheck`, and `npm test`. The private workspace links the shared package locally; published dependency metadata uses the release version. The README's TypeScript examples are checked with the public package declarations.\n\n<!-- File: packages/control-client/rust/README.md (Tokens: ~1436 | Category: plugin-manifest) -->\n# Rust control client\n\n`ControlConnection` discovers whether an existing control endpoint supports framed CBOR or legacy JSON. `ControlClient` is the explicitly framed `Client<ControlProtocol>` entry point, and `JsonControlClient` is an explicit legacy unary adapter. Their checked helpers share the same operation records.\n\n```text\nControlConnection -- JSON capabilities --+-- advertised CBOR: fresh stream + hello\n                                        |                      |\n                                        |               ControlClient\n                                        |\n                                        +-- legacy: JsonControlClient\n```\n\nAutomatic discovery has one deadline across the probe, redial, and hello. It selects CBOR only after a valid affirmative capability response. Malformed replies, arbitrary peer errors, empty EOF, and failed framed setup are errors. There is no error-string downgrade or mutation replay. `ControlClient` sends its hello directly when callers already know the peer supports framed control.\n\n```rust,no_run\nuse microsandbox_control_client::{\n    ControlConnection, ControlMessageType, ControlReply, Empty, GetMemoryState, TypedMessage,\n};\n\nasync fn inspect(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {\n    let connection = ControlConnection::connect(path).await?;\n    let reply = connection.request(TypedMessage::new(ControlMessageType::MemoryState, Empty {})).await?;\n    match reply {\n        ControlReply::Framed(message) => println!(\"{}\", message.t),\n        ControlReply::Json(reply) => println!(\"{} original JSON bytes\", reply.raw().len()),\n    }\n    println!(\"{} MiB\", connection.request_typed(&GetMemoryState).await?.target_mib);\n    connection.close().await;\n    Ok(())\n}\n```\n\nIn framed mode, clones reuse one connection. Automatic JSON mode rediscovers before each fresh exchange when its connector cannot prove runtime identity. An owner implementing `VerifiedControlConnector` verifies the connected peer and the runtime's OS birth token on every dial, then rechecks the active run before each operation; `connect_verified_connector` can reuse that owner's JSON selection. A path or PID alone is insufficient. A detected change invalidates the handle before sending instead of silently retargeting the request. The runtime owner, such as an SDK backend, supplies that identity implementation; the protocol package does not claim to verify it itself.\n\n`JsonControlClient::new(path)` and `from_connector(...)` are inert constructors for callers explicitly choosing legacy mode. Its `request` translates only known native requests and returns the actual `JsonReply`, including `ok:false` replies. Checked helpers report `LegacyRemote` with the original reply and unknown batch progress. JSON numbers retain their original tokens; checked memory fields use `u64` without a floating-point intermediate. `connection.framed()` fails locally in JSON mode, and encoded-payload input is rejected there before dialing. Raw streams and exact packets remain available on the explicitly framed client.\n\n`connection.capabilities()` borrows the released generation-1 discovery projection without network I/O, while `connection.runtime_capabilities()` exposes the complete discovered facility set. `GetCapabilities` remains available for a fresh generation-1-compatible observation and `GetRuntimeCapabilities` requests the complete record on generation 2. `closed().await` observes shared closure, including an idle framed disconnect; a successful JSON exchange ending in EOF does not close the session.\n\nGeneration 1 remains the released capabilities, memory, CPU, and secrets surface. Generation 2 adds `CreateCheckpoint`, `CreateDiskCheckpoint`, `CreateBranch`, `PauseRuntime`, `ResumeRuntime`, `GetPauseState`, `GrowRootDisk`, and `CompactDisks`. `ControlConnection` talking to a framed generation-1 runtime selects the historical JSON representation for these checked operations before sending anything; it never retries a mutation after a framed failure. The explicit `ControlClient` instead returns a not-sent unsupported-operation error. Linux descriptor-backed branch creation remains an SDK-level JSON/`SCM_RIGHTS` exception because ordinary framed messages cannot transfer file descriptors.\n\nEnable `uds` for Unix endpoint connections or `named-pipe` for Windows endpoint connections. Caller-owned transports and connectors can be used without either native feature. Endpoint paths are accepted verbatim.\n\n```rust,no_run\nuse microsandbox_control_client::{\n    ControlClient, GetMemoryState, SetMemoryTarget, size::SizeExt,\n};\n\nasync fn resize(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {\n    let client = ControlClient::connect(path).await?;\n    let before = client.request_typed(&GetMemoryState).await?;\n    let accepted = client.request_typed(&SetMemoryTarget::new(2048.mib())).await?;\n    println!(\"{} -> {} MiB\", before.target_mib, accepted.target_mib);\n    client.close().await;\n    Ok(())\n}\n```\n\nMemory wire records retain `u64` values. The existing size helpers retain their conversion rules; direct `SetMemoryTarget { total_mib }` construction provides full-width wire input. Target responses report acceptance and current observations, not guest convergence. Ordered secret batches preserve partial completion and the index of the first failed entry.\n\nThe client keeps `request`, `stream`, `request_raw`, `stream_raw`, explicit-ID `send`/`send_raw`, exact `write_unchecked`, owned `into_parts`, and checked `request_typed` operations. Generic requests return peer error frames as messages; checked helpers interpret them and retain the original response. No sandbox, execution, filesystem, or convergence service is required to use any of these paths.\n\n```rust,no_run\nuse microsandbox_control_client::{ControlClient, EncodedMessage};\n\nasync fn inspect(client: &ControlClient, payload: Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {\n    let response = client.request(EncodedMessage::new(\"extension.inspect\", payload)).await?;\n    println!(\"{}: {} payload bytes\", response.t, response.p.len());\n    Ok(())\n}\n```\n\nSetup defaults to one ten-second deadline and requests to a thirty-second local wait. Request expiry does not cancel remote work or trigger replay. `ClientError` retains `NotSent` versus `Unknown` delivery. Clones and owned streams share a connection; `close` closes it for all owners.\n\nRun `cargo test -p microsandbox-control-client --all-features --locked`. For live tests, set `MSB_CONTROL_TEST_SOCKET` and `MSB_CONTROL_TEST_MODE=cbor` or `json`. The explicitly framed test requires CBOR; run only `live_automatic_discovery_and_explicit_json -- --ignored --exact` against a JSON-only runtime. Use bounded disposable fixtures (512 MiB and two CPUs), and run live tests serially. The automatic test changes targets and restores them through explicit JSON. Optional `MSB_CONTROL_TEST_SECRET` names a dummy fixture initially set to `before` and allowed for `example.invalid`; the test exercises legacy partial-failure reporting and restores that fixture. Skipped live tests are not runtime or platform validation.\n\n<!-- File: packages/control-client/typescript/README.md (Tokens: ~1886 | Category: plugin-manifest) -->\n# TypeScript control client\n\n`ControlClient` is `Client<ControlProtocol>` over the shared framed engine. It sends a bounded `control.hello` directly, negotiates generation and limits, and exposes the original welcome frame. Use it when the peer is known to support framed control. It does not probe JSON or downgrade after failure.\n\nThe root entry is browser-safe and accepts a caller-owned byte transport or reconnectable connector. The `/node` entry accepts an existing Unix socket or Windows named-pipe path without renaming it.\n\n```ts\nimport { GetMemoryState, MiB, SetMemoryTarget, typedMessage } from \"@microsandbox/control-client\";\nimport { connectFramedControl } from \"@microsandbox/control-client/node\";\n\nasync function inspectAndResize(path: string) {\n  const client = await connectFramedControl(path, { setupTimeoutMs: 10_000 });\n  try {\n    const response = await client.request(typedMessage(\"control.memory.state\", {}));\n    console.log(response.type, response.raw.body);\n    const before = await client.requestTyped(new GetMemoryState());\n    const accepted = await client.requestTyped(new SetMemoryTarget(MiB(2048)));\n    console.log(before.target_mib, accepted.target_mib); // bigint, even for small values\n  } finally {\n    await client.close();\n  }\n}\n```\n\nGeneration-1 checked helpers cover capabilities, memory state/target, CPU state/target, and ordered secret updates. Generation 2 adds `GetRuntimeCapabilities`, `CreateCheckpoint`, `CreateDiskCheckpoint`, `CreateBranch`, `PauseRuntime`, `ResumeRuntime`, `GetPauseState`, `GrowRootDisk`, and `CompactDisks`. They validate replies and retain the actual response on `ControlClientError`. Generic message requests return peer error frames directly. A target reply reports acceptance and observation; it does not establish guest convergence. Secret batches stop at the first failure and retain earlier completed changes.\n\nUse `connectControl` for automatic discovery on the same endpoint. It sends the existing read-only JSON capabilities request, closes that exchange, and opens a fresh framed connection only after an affirmative CBOR advertisement. Setup has one total deadline across both connections. Malformed discovery, timeout, EOF without a reply, or a failed welcome never trigger fallback or replay.\n\n```text\nconnectControl(path)\n    |\n    +-- JSON capabilities --> valid legacy reply --> fresh JSON exchange per operation\n    |\n    +-- JSON capabilities --> advertises CBOR -----> fresh hello/welcome --> shared framed client\n```\n\n```ts\nimport { GetMemoryState, MiB, SetMemoryTarget, typedMessage } from \"@microsandbox/control-client\";\nimport { connectControl } from \"@microsandbox/control-client/node\";\n\nasync function compatibleResize(path: string) {\n  const connection = await connectControl(path);\n  try {\n    const reply = await connection.request(typedMessage(\"control.memory.state\", {}));\n    if (reply.kind === \"cbor\") console.log(reply.frame.raw.body);\n    else console.log(reply.reply.raw, reply.reply.value.get(\"memory\"));\n    const before = await connection.requestTyped(new GetMemoryState());\n    const accepted = await connection.requestTyped(new SetMemoryTarget(MiB(2048)));\n    return { before, accepted };\n  } finally {\n    await connection.close();\n  }\n}\n```\n\n`ControlConnection.connectConnector` accepts a repeatable caller connector from the browser-safe root. The connection exposes the read-only generation-1 `capabilities` projection, the complete `runtimeCapabilities` discovery snapshot, `mode`, `clone()`, `isClosed()`, `close()`, `request()`, and `requestTyped()`. `framed()` returns the complete generic surface in CBOR mode and fails locally with `unsupported_mode` in JSON mode. Encoded messages likewise fail locally in JSON mode; the adapter translates only known, validated native operations.\n\nWhen a framed peer selects generation 1, `ControlConnection.requestTyped()` sends generation-2 checked operations through their historical JSON form before any mutation is admitted. An unverified connector rediscovers the endpoint format first. This path is never a retry after a CBOR failure. The explicit `ControlClient` rejects those operations locally against a generation-1 welcome. Linux descriptor-backed branch creation remains outside ordinary CBOR because it requires `SCM_RIGHTS`; generation 2 covers direct local branching without descriptor transfer.\n\n`JsonReply` contains the original response line, including its delimiter and whitespace, and a lossless `Map` of fields. `JsonNumber.token` retains each original numeric token; checked memory observations become `bigint` without passing through a JavaScript number. Ordinary requests return `ok:false` replies. Checked helpers raise `legacy_remote` with the original reply and unknown batch progress. Neither arbitrary diagnostic strings nor legacy errors are converted into structured CBOR errors.\n\nFor explicit legacy access, construction is inert and performs no discovery:\n\n```ts\nimport { GetCpuState } from \"@microsandbox/control-client\";\nimport { jsonControl } from \"@microsandbox/control-client/node\";\n\nasync function inspectLegacy(path: string) {\n  const client = jsonControl(path);\n  try {\n    return await client.requestTyped(new GetCpuState());\n  } finally {\n    await client.close();\n  }\n}\n```\n\nThe equivalent root API is `JsonControlClient.fromConnector(connector, options?)`. Ordinary legacy request and response lines keep their existing size contract; only automatic discovery replies have a 64 KiB limit. Preparing a secret batch snapshots its entries without imposing the framed 4 MiB ceiling on JSON. JavaScript strings remain subject to garbage collection; explicit buffer clearing is not a guarantee that all copies have been erased.\n\nA standalone automatic JSON connection rediscovers before each fresh operation because an endpoint path cannot establish process identity. `ControlConnection.connectVerifiedConnector` permits discovery reuse when the caller supplies a `VerifiedControlConnector`: `connect` must verify every connected peer against the saved OS process birth identity, and `verifySession` must check the active run and process before each operation. Both receive a deadline and cancellation context. Runtime replacement raises `runtime_changed` before mutation admission and closes the shared handle. This extension point does not implement an SDK database or OS identity verifier by itself.\n\n`MiB`, `GiB`, `KiB`, `TiB`, and `Mebibytes` are exported from `@microsandbox/types/size`. Their arithmetic matches the SDK helpers. `SetMemoryTarget` validates integer precision and range before converting to the wire quantity; it also accepts `bigint` for full-width values.\n\nAll generic operations remain available:\n\n| Need | Method |\n|---|---|\n| Native or encoded payload | `request(typedMessage(...))`, `request(encodedMessage(...))` |\n| Exact opaque envelope | `requestRaw(flags, body)` |\n| Message or raw subscription | `openStream(...)`, `openStreamRaw(...)` |\n| Owned sender and receiver | `stream.split()` |\n| Follow-up using an owned ID | `sendOnStream(id, ...)`, `sendRaw(id, ...)` |\n| Exact packet without a subscription | `writeUnchecked(bytes)` |\n| Optional checked unary operation | `requestTyped(request)` |\n| Shared connection ownership | `clone()`, `close()` |\n\n```ts\nimport { ControlClient, encodedMessage, type ByteTransport } from \"@microsandbox/control-client\";\n\nasync function inspectExtension(transport: ByteTransport, payload: Uint8Array) {\n  const client = await ControlClient.connectTransport(transport);\n  try {\n    const reply = await client.request(encodedMessage(\"extension.inspect\", payload));\n    return { name: reply.type, payload: reply.payload, original: reply.raw };\n  } finally {\n    await client.close();\n  }\n}\n```\n\nThe default control request deadline is 30 seconds. Expiry abandons the local wait; it does not cancel remote work or retry it. `ClientError.delivery` distinguishes requests that were not admitted from unknown outcomes. Complete idle connections do not expire automatically. Explicit close closes every clone; JavaScript finalization is only best-effort cleanup.\n\nFrom `packages`, run `npm run build`, `npm run typecheck`, and `npm test`. Live validation is deliberately opt-in: set `MSB_CONTROL_TEST_SOCKET` to a disposable framed runtime's socket and run `npm test -w @microsandbox/control-client`. Add `MSB_CONTROL_TEST_MODE=cbor` to run automatic discovery as well. The package serializes test files because live files mutate and restore the same VM's targets. A skipped live test is not platform or runtime compatibility evidence.\n\nFor a historical JSON-only runtime, set `MSB_CONTROL_TEST_MODE=json` and run only the compatibility live file with `npm exec -w @microsandbox/control-client -- vitest run tests/compatibility-live.test.ts`. It checks automatic and explicit JSON access, concurrent reads, full-width result types, and accepted memory/CPU targets, then restores the original targets. These running-runtime tests do not establish SDK launch compatibility.\n\nThe additional secret test requires `MSB_CONTROL_TEST_SECRET` naming a dummy fixture initially set to `before` and allowed for `example.invalid`. It exercises partial completion, malformed batches, and legacy JSON failures, then restores those fixture values.\n\n<!-- File: packages/microsandbox-types/README.md (Tokens: ~895 | Category: plugin-manifest) -->\n# microsandbox types\n\nShared task and wire contract types for microsandbox.\n\nThese packages define the backend-neutral shapes that describe what a sandbox should be: its rootfs source, resources, mounts, patches, network, lifecycle, and the cloud HTTP request/response bodies that carry those specs over the wire. Everything that has to agree on a sandbox's shape (the Rust SDK, the CLI, the cloud API, and the TypeScript front end) depends on these contracts instead of redefining them.\n\nThis is a contract layer, not a runtime. It does not create, start, or talk to sandboxes. It only models the data those operations exchange.\n\n## Layout\n\n```text\npackages/microsandbox-types/\n├── rust/\n└── typescript/\n```\n\n- `rust/` publishes as `microsandbox-types` (crate name `microsandbox_types`). It is the source of truth for every shared type.\n- `typescript/` publishes as `@microsandbox/types`. Its `src/cloud.ts` (entry) and `src/domain.ts` are generated from the Rust types, never hand-edited; only the cloud contract and the domain types it references are emitted.\n\n## Source Of Truth\n\nThe Rust crate owns the definitions. The TypeScript bindings are derived from them with [`ts-rs`](https://github.com/Aleph-Alpha/ts-rs) behind the crate's `ts` feature, so the two stay byte-for-byte aligned.\n\n```text\nrust/lib/*.rs  ──(ts-rs)──▶  typescript/src/{cloud,domain}.ts\n```\n\nTo regenerate the bindings after changing a Rust type:\n\n```bash\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate\n```\n\nCI runs the same generator with `--check` and fails when the checked-in bindings drift:\n\n```bash\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate -- --check\n```\n\n## What Lives Here\n\n- Sandbox specs: `SandboxSpec`, `SandboxResources`, `SandboxRuntimeOptions`, rootfs sources, mounts, patches, init, lifecycle policy.\n- Networking intent: `NetworkSpec`, published ports, protocols.\n- Volumes and snapshot manifests: `VolumeSpec`, `SnapshotSpec`, and their kinds.\n- Exec and logging: `Rlimit`, `RlimitResource`, `LogSource`, `SandboxLogLevel`.\n- Cloud wire contracts: the source-tagged `CloudCreateSandboxRequest` union,\n  `CloudSandbox`, snapshot resources and operations, and\n  paginated/message/error bodies.\n\nCloud snapshot contracts distinguish the durable resource from the asynchronous\ncapture operation:\n\n- `CloudCreateSnapshotRequest` is discriminated by `kind`. Its current `disk`\n  variant captures the writable disk, and the enum can gain other snapshot\n  kinds without changing the envelope shape.\n- `CloudSnapshot` uses the same kind discriminator and includes its canonical\n  manifest, byte size, labels, and `CloudSnapshotLocation`.\n- `CloudSnapshotLocation` is either a managed artifact ID or a host-volume\n  path. The same type is used when restoring a sandbox so location semantics\n  are not duplicated.\n- `CloudSnapshotOperation` carries the requested kind and tracks capture\n  through `queued`, `in_progress`, `succeeded`, or `failed`; `result` is\n  populated on success.\n- Validation helpers: sandbox-name and hostname rules shared across SDK, CLI, and cloud.\n\nBackend-private materialized state (registry credentials, local cache paths, DB rows, resolved manifest digests, process handles) deliberately stays out of these packages. See each language's README for details.\n\n## Cloud Request Compatibility\n\nCloud request objects ignore unknown fields, including fields inside network and runtime options. This allows newer SDKs to send additive options before the receiving Cloud release understands them. Unknown settings have no effect until that server supports them; request success does not confirm that every supplied setting was applied.\n\nMissing fields retain their documented defaults. Required fields, recognized field types and values, and enum variants still undergo validation. Ignoring unknown object keys does not make new enum variants, renamed fields, or changed field types compatible.\n\nThis policy applies to public Cloud wire types, not host or operator configuration. SDK checks for runtime-only options remain separate from the server's treatment of future Cloud fields.\n\n<!-- File: packages/microsandbox-types/rust/README.md (Tokens: ~959 | Category: plugin-manifest) -->\n# microsandbox-types\n\nShared task and wire contract types for microsandbox.\n\nThis crate is the source of truth for the backend-neutral shapes that describe a sandbox and the cloud HTTP bodies that carry them. The Rust SDK, the CLI, and the cloud backend all import these types so they agree on one definition instead of duplicating wire shapes. The generated `@microsandbox/types` TypeScript package is derived from this crate.\n\nIt is a leaf dependency by design. It pulls in `serde`, `serde_json`, `chrono`, `sha2`, and `thiserror`, and nothing from the local VM machinery (no runtime, image, network, or database crates). That keeps it cheap enough for front-end generation, cloud API models, and SDK wrappers to all depend on.\n\n## What This Crate Owns\n\nThe crate models durable user and wire intent: what the user wants to exist, not how a backend fulfills it.\n\n- **Sandbox spec** (`domain` module): `SandboxSpec`, `SandboxResources`, `SandboxRuntimeOptions`, `EnvVar`, `SandboxPolicy`.\n- **Rootfs sources**: `RootfsSource` (bind, OCI, disk image), `OciRootfsSource`, `DiskImageFormat`, `PullPolicy`.\n- **Mounts and patches**: `VolumeMount`, `MountOptions`, `StatVirtualization`, `HostPermissions`, `Patch`, `SecurityProfile`.\n- **Volumes and snapshots**: `VolumeSpec`, `VolumeKind`, `NamedVolumeCreate`, `NamedVolumeMode`, `SnapshotSpec`, and the canonical `SnapshotManifest` schema.\n- **Networking**: `NetworkSpec`, `PublishedPortSpec`, `PortProtocol`.\n- **Exec and logs**: `Rlimit`, `RlimitResource`, `SandboxLogLevel`, `LogSource`, `HandoffInit`.\n- **Cloud wire contracts** (`cloud` module): sandbox, snapshot, pagination, message, and error request/response types.\n- **Validation** (`validation` module): `validate_sandbox_name`, `validate_hostname`, `hostname_from_sandbox_name`, and the `MAX_SANDBOX_NAME_BYTES` / `MAX_HOSTNAME_BYTES` limits.\n\nBackend-private materialized state stays out: registry credentials, local CA paths, replace flags, pull-discovered manifest digests, snapshot upper paths, process handles, and DB rows belong to the SDK and backends, not the contract.\n\n## Usage\n\n```toml\n[dependencies]\nmicrosandbox-types = \"0.7.0\"\n```\n\n```rust\nuse microsandbox_types::{RootfsSource, SandboxResources, SandboxSpec};\n\nlet spec = SandboxSpec {\n    name: \"worker\".into(),\n    image: RootfsSource::oci(\"python\"),\n    resources: SandboxResources { cpus: 2, memory_mib: 1024 },\n    ..Default::default()\n};\n```\n\nThe Rust SDK re-exports the contract types it accepts, so most SDK users get these through `microsandbox::*` and do not depend on this crate directly.\n\n## Serialization Notes\n\n- `SandboxSpec`, `SandboxResources`, `SandboxRuntimeOptions`, `NetworkSpec`, and `MountOptions` use `#[serde(default)]`, so partial JSON fills missing fields from static defaults.\n- Lowercase-on-the-wire enums (`StatVirtualization`, `HostPermissions`, `SecurityProfile`, `SandboxLogLevel`, `LogSource`, `PortProtocol`) match the CLI grammar and the TypeScript string unions.\n- `VolumeMount` has hand-written `Serialize`/`Deserialize`. It tags variants with a `type` field and accepts a legacy top-level `readonly` flag, folding it into `MountOptions` on read.\n- Static defaults only. Nothing here reads process-global or profile config; the SDK and backends apply environment defaults before execution.\n\n## TypeScript Generation\n\nThe `ts` feature derives `ts_rs::TS` on exported wire types and builds the `microsandbox-types-generate` binary, which writes `domain.ts`, `snapshot.ts`, and `cloud.ts` beneath `../typescript/src`.\n\n```bash\n# Regenerate the checked-in bindings.\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate\n\n# Verify they are current (used in CI; exits non-zero when stale).\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate -- --check\n```\n\nA unit test (`checked_in_bindings_match_generated_output`) also fails when any generated TypeScript file drifts, so `cargo test --features ts` catches stale bindings too.\n\n## Testing\n\n```bash\ncargo test -p microsandbox-types\ncargo test -p microsandbox-types --features ts\n```\n\n<!-- File: packages/microsandbox-types/typescript/README.md (Tokens: ~811 | Category: plugin-manifest) -->\n# @microsandbox/types\n\nShared task and wire contract types for microsandbox, as TypeScript.\n\nThis package is the type-only mirror of the cloud contract in the `microsandbox-types` Rust crate. It gives TypeScript consumers (the cloud front end and anything talking to the cloud API) the exact cloud wire shapes microsandbox uses, so a `CloudSandboxSpec` means the same thing on both sides of the wire. Only the domain types the cloud contract references are generated; the broader SDK/domain surface is not.\n\nIt contains no runtime code. Every export is a type; importing it adds nothing to your bundle.\n\n## Generated, Not Hand-Written\n\nThree files are generated from the Rust crate with [`ts-rs`](https://github.com/Aleph-Alpha/ts-rs), each carrying a `// @generated` header. Do not edit them:\n\n- `src/cloud.ts` — the package entry: the cloud wire types, which import and re-export their domain and snapshot dependencies.\n- `src/domain.ts` — only the domain types the cloud twins transitively reference (`EnvVar`, `Rlimit`, `NetworkPolicy`, …).\n- `src/snapshot.ts` — the canonical snapshot manifest schema embedded by cloud snapshot resources.\n\nTo change a shape, edit the Rust type and regenerate from the repo root:\n\n```bash\ncargo run -p microsandbox-types --features ts --bin microsandbox-types-generate\n```\n\n## Install\n\n```bash\nnpm install @microsandbox/types\n```\n\n## Usage\n\nImport the shapes you need with `import type`:\n\n```ts\nimport type {\n  CloudCreateSandboxRequest,\n  CloudSnapshot,\n  CloudSnapshotOperation,\n} from \"@microsandbox/types\";\n\nfunction createSandbox(request: CloudCreateSandboxRequest) {\n  // POST the source-tagged request to the cloud API\n}\n```\n\n`CloudCreateSnapshotRequest` and `CloudSnapshot` are unions discriminated by\n`kind`; the current public variant is `\"disk\"`. `CloudSnapshotOperation` carries\nthat same kind and exposes only the public statuses `queued`, `in_progress`,\n`succeeded`, and `failed`. Both snapshot creation results and disk-snapshot\nrestore requests use `CloudSnapshotLocation` to distinguish a managed artifact\nID from a host-volume path.\n\n## Generated Shape Notes\n\nThe bindings follow `ts-rs` conventions, which mirror the Rust serde representation:\n\n- Cloud enums are internally tagged with a `type` field: `CloudRootfsSource` is `{ type: \"bind\"; … } | { type: \"oci\"; reference: string } | { type: \"disk_image\"; … }`, and `CloudVolumeMount` / `CloudHostPattern` / `CloudViolationAction` follow the same shape.\n- `CloudCreateSandboxRequest` is a flat union discriminated by `source: \"oci\" | \"bind\" | \"disk_image\" | \"disk_snapshot\"`. Common sandbox fields stay flat on every variant; source-specific fields such as `reference`, `path`, and `disk_snapshot_ref` appear only where they apply. Servers also accept the legacy `image` request shape without `source` during migration.\n- `CloudCreateSnapshotRequest` and `CloudSnapshot` are flat unions discriminated by the current `kind: \"disk\"`; common snapshot fields stay flat beside the discriminator.\n- Lowercase domain enums like `StatVirtualization` are string-literal unions (`\"strict\" | \"relaxed\" | \"off\"`).\n- Optional Rust fields are `T | null`; fields skipped when absent are `?:` optional.\n- Referenced domain and snapshot types live in `domain.ts` and `snapshot.ts` and are re-exported from the package entry, so a single import from `@microsandbox/types` sees the whole cloud contract.\n\n## Build And Typecheck\n\n```bash\nnpm run build       # tsc -> dist/\nnpm run typecheck   # tsc --noEmit\n```\n\n<!-- File: packages/protocol-client/rust/README.md (Tokens: ~770 | Category: plugin-manifest) -->\n# Generic framed client\n\n`Client<P>` owns one byte connection, its correlation IDs, and all routed subscriptions. `P: Protocol` establishes the transport and selects message metadata and an `EnvelopeCodec`. Protocol consumers can implement both traits outside this crate. `tests/engine.rs` exercises external protocol setup and routing, while `tests/external_codec.rs` supplies an external connector and a non-CBOR envelope codec across native, encoded, raw, checked, stream, explicit-ID, and exact-packet calls. Rust native payload serialization remains CBOR; the custom codec controls its enclosing envelope, and encoded/raw paths retain caller-supplied bytes.\n\n```text\nnative payload ---- TypedMessage ---+\nencoded payload --- EncodedMessage -+--> codec --> shared writer\nraw envelope -----------------------+-----------> shared writer\nexact packet -----------------------------------> shared writer\n\nshared reader --> ID routing --> RawStream --> Stream (decode on recv)\n```\n\n`request` returns the first response. If it is not terminal, the router retains the ID and discards later replies until terminal completion. `request_typed` additionally requires a terminal response and invokes a borrowed `Request<P>` implementation's checked decoder. Application errors remain that implementation's responsibility; the generic engine does not equate a terminal response with success.\n\n`stream` and `stream_raw` return owned subscriptions. `into_parts()` moves them into a cloneable sender and one receiver. Both retain the connection. Dropping or closing the receiver disables sends and drains any admitted operation until terminal completion; it does not send a process signal, file EOF, TCP close, or cancellation packet. Old sender handles cannot gain permission when an ID is reused.\n\n`send` and `send_raw` support explicit IDs owned by the connection. `write_unchecked` serializes caller-owned packet bytes without creating an ID or response subscription. Native and encoded-payload paths apply protocol availability gates; raw paths preserve opaque bytes. A decoded `Message` retains its original `RawFrame`, including unknown envelope fields.\n\nConnections are shared handles. Explicit `close(&self)` closes every handle and wakes waiters. `closed().await` observes closure, including idle peer disconnection, without polling; canceling that wait leaves the connection alone. Last-owner drop closes the transport; the reader and writer do not keep the owner alive. A stream remains an owner after a temporary client handle is dropped.\n\nQueues have item and combined byte limits. A retained slow receiver can backpressure the whole connection; fair per-stream flow control is not provided by the wire protocol. Dropping that receiver releases its queued frames and wakes blocked routing. Active and draining IDs both consume in-flight capacity.\n\n`ClientError.delivery` distinguishes `NotSent` before writer admission from `Unknown` afterward. Timeouts and dropped futures stop local waiting, without retrying or undoing peer work. An EOF before terminal completion is an error; truncation inside a frame is reported separately. Optional incomplete-frame deadlines start at the first byte and do not expire idle connections.\n\nThe `uds` and `named-pipe` features enable `LocalConnector` on their platforms. It uses the endpoint supplied by the caller without inventing names. A custom `Connector` receives an absolute setup deadline and returns an exclusively owned byte stream. `connect_stream` accepts a transport that the caller has already dialed and authenticated.\n\nValidation: `cargo test -p microsandbox-protocol-client --all-features --locked`. The Unix connector test binds a temporary socket and therefore needs an execution environment that permits local socket creation.\n\nProtocols choose whether completed correlation IDs may be reused. Agent connections retire IDs for the lifetime of the connection, matching the relay; control connections may reuse an ID after its terminal response. Exhaustion is a local error and does not reconnect or replay a request.\n\n<!-- File: packages/protocol-client/typescript/README.md (Tokens: ~1370 | Category: plugin-manifest) -->\n# @microsandbox/protocol-client\n\nA protocol-neutral framed client for TypeScript. `Client<P>` owns one byte transport, correlation IDs, bounded queues, a serialized writer, and response routing. `P` owns setup, its envelope codec, and message gates. Agent and control operations stay in their protocol packages; sandbox lifecycle stays in the SDK.\n\n```text\n                 Client<P>\n          /          |           \\\n native payload   encoded      raw frame / exact packet\n          \\          |           /\n          protocol codec + shared router\n                       |\n                ByteTransport\n```\n\n## Implementing a protocol\n\nAn external package can implement setup and metadata using only public declarations. This example protocol reads one ready byte and uses the shared CBOR envelope. Its server must implement that same handshake.\n\n```ts\nimport {\n  Client, CborEnvelopeCodec, readExactly,\n  type Protocol, type ByteTransport, type EstablishContext, type Established, type SendMetadata,\n} from \"@microsandbox/protocol-client\";\n\nclass ExampleProtocol implements Protocol<number> {\n  async establish(transport: ByteTransport, context: EstablishContext): Promise<Established<number>> {\n    const ready = (await readExactly(transport, 1, context.signal))[0]!;\n    return {\n      transport, ready, codec: new CborEnvelopeCodec(), limits: context.limits,\n      ids: { start: 1, endExclusive: 2 ** 32 },\n    };\n  }\n  prepare(ready: number, _wireName: string): SendMetadata {\n    return { generation: ready, flags: 0 };\n  }\n}\n\nasync function connect(transport: ByteTransport) {\n  return Client.connectTransport(transport, new ExampleProtocol(), { setupTimeoutMs: 10_000 });\n}\n```\n\n`ByteTransport.read(maxBytes)` returns between one and `maxBytes` ordered bytes, or `null` for clean EOF. The engine makes one read at a time and independently serializes writes. `close()` must wake both directions. Passing a transport transfers ownership, including when setup fails. `Connector.connect(context)` supplies repeatable dialing with a deadline and abort signal; it does no discovery itself. `LocalConnector` and `NodeTransport` live at `@microsandbox/protocol-client/node`. Browser-safe `WebSocketConnector` and `WebSocketTransport` are root exports.\n\n`EnvelopeCodec.encodePayload()` supports protocol-specific native encoders. `encode()` wraps supplied payload bytes; `decode()` returns an inspectable `InboundFrame` retaining its `raw` frame. The standard codec uses `{ v, t, p }`, with `p` an actual CBOR byte string, strict unsigned integers, and open message names. Raw subscriptions never invoke that codec.\n\n## Public access levels\n\n| API | Input and result |\n| --- | --- |\n| `request(typedMessage(name, value))` | Native payload; first `InboundFrame` reply |\n| `request(encodedMessage(name, bytes))` | Pre-encoded application payload; first message reply |\n| `requestTyped(request)` | Optional checked request and result decoder; requires terminal reply |\n| `openStream(message)` | Owned native stream through terminal |\n| `requestRaw(flags, body)` / `openStreamRaw(flags, body)` | Opaque envelope bodies and raw replies |\n| `sendOnStream(id, message)` / `sendRaw(id, flags, body)` | Explicit ID owned by this connection |\n| `writeUnchecked(bytes)` | Exact transport bytes without framing or semantic checks |\n| `stream.split()` | Separate owned sender and single receiver |\n| `client.ready` / `clone()` / `close()` | Protocol metadata and shared connection lifetime |\n\nChecked helpers are optional; neither native messages nor raw responses are narrowed to a generated message enum. Application errors remain distinct from `ClientError`. Unknown message names, payload bytes, and envelope fields remain inspectable.\n\n## Limits, ownership, and cancellation\n\nDefault engine limits are 4 MiB per frame, 1024 in-flight exchanges, 256 queued writes, 1024 queued responses per exchange, and 8 MiB of combined engine-owned packet buffering. Protocol setup can select tighter limits. Counts exclude caller-owned inputs, returned frames, and operating-system or browser socket buffers. Native transports read in paused mode; the WebSocket adapter separately caps its input queue and closes on overflow.\n\nA setup deadline covers dialing and protocol establishment. Optional request deadlines cover writer admission and local response waiting; incomplete-frame deadlines start only when a frame begins. Idle connections have no implicit incomplete-frame deadline. `AbortSignal` cancels local waiting. `delivery: \"not_sent\"` means an attempt never entered the writer; `\"unknown\"` means it may have reached the peer. The engine never retries an admitted operation.\n\nClosing a stream only stops local delivery. Nonterminal abandoned exchanges retain their IDs until terminal; stale split senders remain invalid after ID reuse. A receive timeout leaves future frames available. Shared `client.close()` wakes every handle and closes the transport. Close explicitly in `finally`: JavaScript finalization is best-effort, while explicit close is deterministic.\n\n## Development\n\nFrom `packages`, run `npm ci`, `npm run build`, `npm run typecheck`, and `npm test`. Tests exercise an externally defined protocol, full-width IDs, byte preservation, routing, cancellation and admission, ownership, backpressure, transport truncation, WebSocket bounds, and public README examples. Live runtime and historical SDK compatibility tests are additional release requirements.\n\nTo check package exports independently of workspace resolution, install locally packed client packages into a separate consumer directory, then run `node protocol-fixtures/check-packed-protocol-consumer.mjs /path/to/consumer` from `packages`. The consumer lockfile must identify the protocol-client tarball and integrity, and the installed package must not be a workspace symlink. This compiles and executes a custom connector, non-CBOR envelope codec, native/encoded/raw/checked calls, full-width IDs, split streams, explicit sends, and exact packet writes against the installed declarations.\n\nThe command prints a retained output directory. With the packed agent and control packages also installed in that consumer, pass that output directory to `node protocol-fixtures/check-browser-package-roots.mjs /path/to/consumer/protocol-consumer-output`. This bundles all three root exports for the browser, rejects Node builtin imports, and executes the same protocol consumer without Node globals. It verifies browser package loading; actual browser/WebSocket integration remains a separate check.\n\nProtocols choose whether completed correlation IDs may be reused. Agent connections retire IDs for the lifetime of the connection, matching the relay; control connections may reuse an ID after its terminal response. Exhaustion is a local error and does not reconnect or replay a request.\n</yakaai_skills>"}