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.