Skip to content

A sandbox's CLI reaches its owner, and its record carries its truth - #246

Open
geekgonecrazy wants to merge 4 commits into
devfrom
atomic/sandbox-stack
Open

geekgonecrazy wants to merge 4 commits into
devfrom
atomic/sandbox-stack

Conversation

@geekgonecrazy

@geekgonecrazy geekgonecrazy commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

The token-granted remote-sandbox stack, the CLI that runs inside one, and the record contract that keeps a write honest — rebased onto dev as one squashed delta (dev's #239 libatomic and #243–#249 are preserved; the CLI service area, diff/log rendering and build identity come from dev, everything below is added).

Sandbox grants, with a host-store seam

libatomic's DaemonState holds grants in memory by default; a host that persists them installs its own store before serving (DaemonState::with_sandbox_grants). Tokens are minted blake3-hashed, carry a view, a capability subset and a server-clock expiry (TTL bounded, default 7200s); renew_sandbox extends the expiry and close_sandbox revokes. Every request bearing a sandbox token is resolved against the store — a token that does not resolve is refused, per request.

The CLI inside a remote sandbox

A pointer is a working root (.atomic-sandbox, mode 0600 — it carries a credential); atomic sandbox materialize reconstructs the view into the sandbox's cache; its writes submit to the serving repository over the daemon socket, carried there by whatever the host serves (a forwarded socket, a direct connection). Nothing opens a repository the sandbox does not have, and a CLI that loses its socket fails (ATOMIC_DAEMON_BIN=/usr/bin/false) instead of starting a daemon on the cache. Only the commands that work off the cache run there; the rest refuse, naming the sandbox and why.

The remote sandbox's cache, without the owner protocol

View skeletons and graph slices import from submitted bytes; a sandbox that fell behind materializes again before it records. The cache's perspective mirrors the ancestor walk exactly — pinned by a parity test against the repository's own perspective.

triage review serves the report

The full bundle — the verdict, the findings with their remedies, the intents and their criteria, the candidate changes with their diffs, the guided walkthrough — rides GenerateTriageReview as versioned atomic.triage.report.v1, so the CLI (and any web UI over the service) renders the same document the local builder produced. The CLI-entangled triage builder moved into the repository (triage/{mod,model,report}), served instead of shelled.

The write carries its truth

A record carries its AI authorship (vendor, model, tool, suggestion type, tokens, session) into the change's provenance — Provenance::from_authorship_parts. And it fences on the view's state: the caller passes the state it last saw (expected), the response's view_merkle chains the next write, and a stale expectation refuses VIEW_STALE instead of landing over someone else's change. Insert fences on the target view's snapshot the same way; dry runs read only and don't fence. (Dev's #248 working-copy protection sits beside the fence in the same handler — different hazard: it guards a dirty working copy, the fence guards a moved view.)

A turn's cost and conversation ride its record

The sherpa turn-end hook reports the turn's tokens and step count; the change's unhashed agent_turn carries them with the condensed transcript — the prompt, the tool calls, the closing reply — attached before serialization so the local store and a sandbox's submitted bytes are the same document. A bare-string assistant reply (how a text-only turn serializes) is a turn of the conversation; dropping it left provenance ending at the last tool call.

Related

Verified

  • cargo test --workspace: 9222 passed, 0 failed — including the remote-sandbox CLI e2e (atomic-cli/tests/remote_sandbox_client_test.rs: the real binary inside a pointer-only tree over a Unix socket — token scoping, intent writes landing with their record, provenance publishing, the transcript's cost landing on the change; the same binary host-side through the same server), the sandbox service suite (open/renew/close, materialize, submit — the token gate, the fences), the triage service suite, the record service suite (authorship, fence, view_merkle chaining), and the repository parity tests for the sandbox cache.

The token-granted remote-sandbox stack, the CLI that runs inside one,
and the record contract that keeps a write honest.

- **Sandbox grants, with a host-store seam**: libatomic's DaemonState
  holds them in memory by default; a host that persists them installs
  its own store before serving. The ingress serves sandbox tokens only.
- **The CLI inside a remote sandbox**: a pointer is a working root, the
  view materializes into the sandbox's cache, and its writes submit to
  the owner over the socket — nothing opens a repository the sandbox
  does not have, and a lost socket fails instead of starting a daemon on
  the cache (`ATOMIC_DAEMON_BIN=/usr/bin/false`).
- **The remote sandbox's cache**, without the owner protocol: view
  skeletons and graph slices imported from submitted bytes, a sandbox
  that fell behind materializes again before it records.
- **`triage review` serves the report**: the full bundle (verdict,
  findings, intents and criteria, changes with real diffs, the
  walkthrough) rides GenerateTriageReview as versioned
  `atomic.triage.report.v1`, so the CLI renders over the service what
  the local builder produced.
- **The write carries its truth**: a record carries its AI authorship
  (vendor, model, tool, suggestion, tokens, session) into provenance,
  and fences on the view's state — the response's `view_merkle` chains
  the next write; a stale expectation refuses VIEW_STALE instead of
  landing over someone else's change. Insert fences on the target view
  the same way.
- **A turn's cost and conversation ride its record**: the sherpa
  turn-end hook reports the turn's tokens and step count, and the
  change's unhashed agent_turn carries them with the condensed
  transcript — the prompt, the tool calls, and the closing reply (a
  bare-string assistant turn included) — attached before
  serialization, so the local store and a sandbox's submitted bytes are
  the same document.

Verified: `cargo test --workspace` — 9208 passed, 0 failed — including
the remote-sandbox CLI e2e (token scoping, intent writes landing with
their record, provenance publishing, the transcript's cost landing on
the change) and the sandbox and triage service suites.
… private doc links

- record/mod.rs: the Base32 import and the mut on the record outcome were
  left over from removing the attach-then-resave path — both gone.
- diff_engine.rs: two doc blocks orphaned by the triage move (the stat
  graph and the word-diff renderer live in the CLI now) were interleaved
  before change_file_diffs's own docs, tripping
  empty_line_after_doc_comments — removed.
- intra-doc links to private or moved items (export_crdt, layer_key,
  effective_state, super::output) unlinked — the doc job runs
  rustdoc with -D warnings.
- the remote-sandbox e2e binds a Unix socket; the file is now
  cfg(not(windows)), the same gate the other socket-based integration
  tests carry.
- a needless borrow in the triage service test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant