Agent Runtime — The End Shape
Settled 2026-07-28 and corrected by ADR-046 on 2026-08-05. Humanwork's turn boundary is the exact inbound message; Hermes owns context, tools, retrieval, and response behavior.
One org container runs one process tree and touches no real filesystem for anything a session owns. At the root sits the zig binary — hermes-run. It authenticates admission, applies Landlock confinement, and confined-execs one resident Bun machine per supervisor. The compiled Bun machine carries CPython and the entire Hermes payload inside its own bunfs, executed from memory via memfd; nothing is unpacked to disk. It then uses Bun.spawn for one native Hermes child per conversation holder.
That Bun process opens one organization AgentFS replica backed by the organization's Turso Cloud AgentFS. bunfs contains private executable/runtime packaging only; it is not an agent-visible filesystem layer. Organization persona/config defaults, shared skills and memory, and every conversation's private config, logs, attachments, generated files, and workspace live directly in that one database. The API accesses the cloud authority directly; conversation Hermes children share scoped views of the resident Bun process's local replica.
Two userspace routes serve that store to the running agent. Hermes' ordinary file operations under $HERMES_HOME, /workspace, /artifacts, and /tmp are answered by the interpose shim, which redirects them into the conversation-scoped AgentFS view — Hermes sees ordinary paths; they are database objects. Shell workspace operations use just-bash, running inside Bun itself against the same AgentFS handle, so ls and cat never reach a host workspace. There is no host cwd, TMPDIR, scratch, staged workspace, or host home carrying session data; the supervisor socket is the only named host-side control artifact.
The one thing that isn't a file even conceptually — the conversation transcript — is DB-native: the organization has one separate SessionDB containing all native Hermes sessions. Native session_id values distinguish conversations. SessionDB is not stored inside AgentFS. Any later authorized reader — API conversation read, exact-locator Expert correction, or delivery reconciliation — reads that same authority. The API serializes canonical rows transiently into the authorized response only; it does not persist a mirrored transcript, rebuild prose in Postgres, or maintain a projection/cache as another history authority.
Durability is Turso, execution is memory, code is immutable, and there are no mounts, no devices, no privileges — so the identical architecture runs on local Docker, Fargate, and Railway.
The stack, one line per layer
zig (hermes-run) confinement + supervisor lifecycle
└─ one resident Bun CPython + Hermes payload on immutable bunfs
├─ shared org AgentFS writable overlay delta (Turso Cloud authority)
│ ├─ scoped $HERMES_HOME, /workspace, /artifacts, /tmp via interpose
│ └─ just-bash + HyperSH Git use the same scoped merged filesystem
└─ Bun.spawn Hermes child per conversation; shared org SessionDB authority
The machine starts Hermes with Bun.spawn, preserving the raw ACP relay and fd
3-7 payload/channel mapping. Normal Hermes tools remain normal: terminal,
filesystem, HyperSH Git, Chromium/browser, direct network/web, image, and other
configured providers. Hermes progressively discloses nonessential tools. MCP
adds only a capability absent from the normal tool surface.
For a turn, Humanwork passes the actual inbound message as native ACP content blocks: exact text stays a text block and an attachment stays its own standard resource/image/audio message. It never adds a second prompt, synthetic file descriptor, history, KB block, tool catalog, directive block, or delivery policy around the message. The supervisor raw-relays those blocks and SessionDB supplies continuity directly to Hermes.
An attachment is one AgentFS object addressed by a standard
file:///artifacts/inbound/... or file:///workspace/... URI. The API authenticates the source,
writes that object directly to cloud AgentFS, then sends the attachment as its
own native ACP resource block referencing that AgentFS-visible URI. Hermes ACP
handles the block natively while the interposer serves /workspace from the
resident AgentFS handle. Outbound
files remain those AgentFS objects and are exposed through an authorized API
reader by URI; they are not copied into R2, base64, a shared dropbox, a
completed-turn duplicate, or a host directory. hermes-run carries ACP only
and never proxies a file operation or file byte.
Conversation freshness is event-driven by exact Hermes locators. AgentFS cloud sync uses Turso's native work boundary: plain pull, local work, another pull that atomically applies remote changes and replays unpushed local changes, then push when local CDC is pending. Remote collisions are last-push-wins. There is no long poll, timer, Humanwork revision, or application merge. Authorized clients apply product events or invalidate one scoped cache entry. There is no application interval polling, periodic full-transcript export, or read-to-detect-change loop. An explicit open, reload, or refresh may perform one bounded authoritative read.
What the interpose shim is
The interpose shim is a small shared library (runners/hermes-binary/interpose/interpose.zig, built as libhermes_interpose.so) loaded into the Hermes process via LD_PRELOAD. It is the mechanism that makes "Hermes believes it has a home directory; it's actually the database" true.
LD_PRELOAD makes the dynamic linker resolve libc symbols to our library before the real libc. The shim exports its own open, openat, stat, access, unlink, opendir, readdir… — so when CPython (and therefore Hermes) calls open("$HERMES_HOME/config.yaml"), our code runs first. It checks the path: if it is under the virtual home, it never touches a host filesystem — it asks the Bun machine over a channel fd for that file from the active conversation's private AgentFS root, gets the bytes, puts them into an anonymous in-memory fd (memfd), and hands that fd back to Python. Writes go the same way in reverse into AgentFS. Calls outside the virtual paths remain subject to Landlock's narrow immutable-runtime/kernel allowlist; they do not expose a host workspace, home, scratch directory, or mutable session path.
It also serves a handful of static system files (/etc/hosts, /etc/resolv.conf, /etc/os-release, nsswitch.conf) from baked-in content — that is how the confined process resolves hostnames like the model gateway and the Turso sync endpoints without any real /etc (an entry missing from the synthetic hosts content hangs every dependent call silently, which has bitten twice: the model gateway, then the SessionDB sync host).
Its limits are why the design falls where it does: it can only intercept what goes through libc symbols — a Rust extension doing its own syscalls partially bypasses it (the measured state.db-info.tmp split-brain), and it cannot fake mmap-coherent -shm files or byte-range locks. Files it handles perfectly; a live database engine it structurally cannot — hence SessionDB going DB-native over its own Turso connection instead of through the shim.
In the layer picture: the shim is the userspace replacement for a kernel mount — the "mount" that needs no /dev/fuse, no capabilities, and therefore runs on Fargate and Railway.
Why not a kernel mount
FUSE needs /dev/fuse and CAP_SYS_ADMIN; ECS Fargate rejects both at task definition time and Railway offers neither. A kernel-mount design is structurally undeployable on the production targets. The deployable form of "AgentFS mounts" in a plain container is userspace mounting — the shim for the home, bun/just-bash for the workspace — which needs zero privileges and is what this repository implements.
Why SessionDB is not a file in AgentFS
A live database engine needs kernel file semantics (shared -shm mmap, byte-range locks, fsync ordering, atomic rename) that an RPC file bridge cannot fake — the attempt produced a measured split-brain (the sync engine's state.db-info.tmp write was intercepted while its rename() hit the real filesystem: ENOENT, connect failed, and transcripts silently stopped persisting). And an outer replicator snapshotting an inner replicator's files cannot know its transaction boundaries — a restored copy can be torn. AgentFS is a Turso database, so SessionDB going directly over its own Turso connection reaches the same durable substrate with transaction-correct, row-level replication.