docs: stateful agent session demo — the sandbox is a value#45
Merged
Conversation
A runnable, deterministic example of the just_bash + vfs + exgit stack as an agent sandbox: four public GitHub repos cloned in pure Elixir (no git binary, in-memory), mounted programmatically one per loop iteration, searched with a single grep whose scope grows with the mount table, then shrunk again with umount. Closes with Exgit.FS.grep streaming matches host-side off the unmounted clone. Also adds examples/**/*.exs to the formatter inputs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H2mmV55riKhWgzBS8G2oMT
…ndary A nonzero exit from any command now raises, so the deterministic transcript genuinely doubles as a smoke test. The lazy-clone comment notes that Exgit.FS.grep requires an eager clone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H2mmV55riKhWgzBS8G2oMT
The Shell helper hid the load-bearing line of the whole stack.
Every exec is now written the way a real caller writes it:
{%{exit_code: 0} = result, bash} = JustBash.exec(bash, cmd) —
execute, destructure, and crash-assert in one match, with the full
Result visible in the MatchError on failure.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H2mmV55riKhWgzBS8G2oMT
Rename multi_repo_grep.exs -> agent_session.exs and restructure into four beats: build the world (mount-per-loop, growing grep), state threading (redirect one exec's output, read it in the next), fork and roll back (checkpoint = session, rm in the live session, checkpoint asserted intact), shrink the world (umount). Drops the host-side Exgit.FS.grep coda — backends now appear only at mount lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H2mmV55riKhWgzBS8G2oMT
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H2mmV55riKhWgzBS8G2oMT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
examples/agent_session.exs: a runnable, deterministic, ~60-line example of driving a just_bash session as a value, with git-backed mounts.No helpers or wrapper modules — every executable line is what a caller writes. Four public repositories (vfs, exgit, just_bash, pyex) are cloned over git smart HTTP v2 in pure Elixir — no
gitbinary, no checkout on disk, no container — and mounted into one session. Every exec is written aswhich executes, threads the session forward, and asserts the exit code in a single match. On failure the
MatchErrorcarries the fullResult, stderr included.What it demonstrates
/repos/<name>and re-runs the samegrep -rln 'defimpl VFS.Mountable, for:' /repos/*/lib. The match list grows with the mount table (2 → 3 → 4 → 6 files); the glob expands across mount roots like ordinary directories. Backends appear exactly once each, as a constructor at the mount line.umountat the end removes pyex from the same grep./work/matches.txt; the next exec reads it back withwc -l. One command's writes are the next command's input, carried entirely by the returned session.checkpoint = session, then the live session removes the file —catexits 1, asserted — while the checkpoint still reads it —catexits 0, asserted. No snapshot API exists because none is needed.The transcript is deterministic — consecutive runs are byte-identical for a given set of repo HEADs — and every command is exit-asserted, so running the example is a smoke test of the stack.
How the libraries interact
just_bash never references exgit, and exgit never references just_bash; both depend on vfs's ten-operation
VFS.Mountableprotocol. The example's mount line crosses all three layers.A session's filesystem is a
%VFS{}mount table —JustBash.mount/3is a one-line delegation:When the agent's grep reads a file, just_bash's commands go through its FS facade, which delegates to vfs:
vfs resolves the longest mount prefix, strips it, and dispatches to whichever backend owns the path. Every operation returns the possibly-updated backend, which vfs threads back into the table — that is how lazy-clone caches survive:
exgit's entire integration is one protocol implementation — paths in, blobs out of the in-memory object store, workspace state threaded back:
So
grep -rln … /repos/*/libresolves as: just_bash's grep command →JustBash.FS→ vfs mount resolution →Exgit.Workspace'sMountableimpl → git blobs. Swap the backend and nothing above the protocol changes — pyex's S3 and overlay filesystems, which this grep finds, mount the same way.Relevance to agent platforms (one repo per session)
Exgit.clone(url)+JustBash.mount/3: an isolated bash environment with the repository at HEAD, fetched straight into memory. No clone on disk, no container image, no shared checkout to coordinate over.umount, or letting the value go out of scope. Cleanup is garbage collection.grep,wc, redirects, pipes, and globs work uniformly across every mount — the tool surface presented to the model is the one it is already trained on, while the host keeps typed control over what is mounted where.Exgit.clone(url, filter: {:blob, :none})starts a session from refs plus trees; blobs fetch lazily on first read, and that cache threads through the session value like all other state.VFS.Mountableimplementations across the mounted repos — including exgit's own workspace impl and pyex's S3 and overlay filesystems. Anything implementing the protocol mounts into the same tree the agent is already searching.Also adds
examples/**/*.exsto the formatter inputs so the example stays format-checked in CI.🤖 Generated with Claude Code
https://claude.ai/code/session_01H2mmV55riKhWgzBS8G2oMT