Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture

Crabtalk is a daemon, a library, and a sandbox. Which of the three you are using decides what you can extend and how — and most design arguments here turn out to be arguments about which one someone means.

The layers

protocol     clients, over a socket    any language, untrusted
harness      what shapes an agent      declared per agent, sandboxed or compiled in
capability   what an agent reaches     bounded by its argument
runtime      lifetime orchestration    turns, conversations, the agent loop
store        data                      one key-value primitive, everything above it free

The line worth getting right is between the middle two.

A harness shapes an agent: what it remembers, what skills it can load, what it knows of its own past conversations, the tools it habitually reaches for. Change them and it is a different agent. That is why they are declared per agent — an agent is its harnesses.

A capability is a mechanism for reaching something that is not the agent: fs and exec for the machine, http for the network, MCP for other software, and berm.call for another harness.

The protocol is what clients speak over the socket. A harness reaches the runtime through one capability per operation instead — peers, sessions, skills — each narrowing by existing rather than by a check run over a decoded message. sessions::search cannot name an agent because it takes a search and returns hits, and there is no field in it to mean anything else.

runtime owns the architecture of lifetime — turns, conversations, the agent loop — and nothing else. store owns the data, and owns it behind a single primitive: implement five key-value methods and every interface above them — agents, sessions, memory, skills, harnesses, search — comes for free.

Sandboxed and compiled in

A harness image is no_std for RV64 and nothing else: a sandboxed ELF the daemon schedules, reaching the world only through capabilities its declaration bounded — the absence of a capability from its linker is the enforcement. There is no std build of one. Off its target the crate still compiles, so cargo test can run the tools natively, but the capabilities there are berm-lang stubs a test arranges rather than anything that reaches out.

A compiled-in harness is native Rust inside the daemon, with the whole of std. MCP and memory are the two. It shares the Harness trait with an image and nothing else — not the source, not the confinement: there is no linker to omit from, so what bounds it is what it was written to do.

So the two are different kinds rather than two builds of one thing, and what decides which a feature can be is state. An image gets a fresh heap every invocation and persists through fs like anything else, so anything holding something alive between calls cannot be one. MCP holds live connections; MCP is compiled in and only compiled in.

Where does a thing go?

Three questions, in order. They are independent, and a feature can want more than one answer.

1. Does the daemon own the state? If not, it cannot be protocol — there is no question a client could ask that the daemon knows the answer to. Web search is the clean example: everyone has it, but the daemon holds no search state, so it is a harness and never a message.

2. Does it shape the agent, or reach past it? What an agent remembers, loads, and knows of its own history shapes it — that is a harness, declared by the agents that want it. A mechanism for touching something outside is a capability, bounded by its argument.

3. Does it hold anything alive between calls? If yes it can only be compiled in, because an image gets a fresh heap per invocation. Persisting through fs does not count — a file outlives the call. A live connection does not.

Arguments, not lists

The recurring rule, and the one worth defending hardest:

The argument is the capability. Without one it is never registered.

root is the argument to fs and exec; hosts is the argument to http. An under-specified declaration reaches nothing rather than everything, and there is no separate list of permitted names to keep in step with it — a list could only ever restate what the arguments already decided, or contradict them.

What a published image calls is the image’s business. One that never calls a capability needs no stopping; one that does is an image for exactly that, and choosing to install it is the decision. A shell-less environment is an image with no shell tool, not a shell tool with its host call withheld.

And a harness never chooses its own scope: SearchSessions carries an agent filter, and the host overwrites it with whoever declared the harness. Refusing a wrong value would only teach the harness to send the right one.

berm is not a crabtalk feature

The sandbox lives in its own repository, berm, and reaches this one from crates.io: berm host-side, berm-lang for guests. Neither knows a crabtalk crate exists.

crates/berm is crabtalk’s side: what surfaces sandboxed tools, and the crabtalk.* capabilities. Anything host-specific belongs there. http lives there rather than in the engine because hyper needs a reactor and the engine is sync and has none — keeping the engine dep-light is keeping it portable.

Prose

The daemon supplies none. An agent’s description is its system message, used verbatim; there is no default prompt and no framing wrapped around it.

What a model needs in order to reach for a tool is the tool’s own description, or the usage its harness declares — a few lines about when to reach for these tools and how they go together, which is the question no single tool description can answer because it is about choosing between them. Usage is declared in .berm.abi and injected only into agents that declared the harness. Anything longer than a few lines is a skill, not usage.