Design 58: agent work as the mesh's own, graduated from research 053 with ADR 0383 to ADR 0390

Threads on the bus, a headless thread handler in a sandbox per session, a workspace toolkit with no
general command runner, one identity seat, effects on every tool, signed bus logins and the switch
to them; names the build's acceptance tests for the milestones.
This commit is contained in:
2026-10-16 02:44:15 +02:00
parent 08d6476e52
commit 8f8f1b4337
2 changed files with 156 additions and 0 deletions
@@ -0,0 +1,155 @@
---
layer: to-be
status: designed
code: []
updated: 2026-10-16
decisions:
- "ADR 0383"
- "ADR 0384"
- "ADR 0385"
- "ADR 0386"
- "ADR 0387"
- "ADR 0388"
- "ADR 0389"
- "ADR 0390"
---
# 58 — Agent work as the mesh's own
**An agent's work belongs to the mesh, not to one vendor's program or one machine. A thread is kept whole on the
bus by a thread seat; a thread handler on an always-on node runs the agent's harness headless for it, in a sandbox
of its own, with the thread's own branch on the forge as its workspace; the agent works through a workspace toolkit
and named tools, never a general command runner. Every person, agent and module is an identity held by one identity
seat, with a role made of grants; the bus checks each login by its signature, and the tool runner checks each call
against the role, tool by tool, by the effect every tool carries.**
(ADR 0383 to ADR 0390, from research 053.)
**Acceptance.** The operator starts a thread from the terminal client, an agent on an always-on node works it in a
sandbox, asks a question the operator answers in place, and the thread moves to another node and carries on with
nothing lost; the agent's calls outside its role are refused, saying which grant is missing.
## 1. The parts
```
any node always-on nodes the control-node
───────── ─────────────── ────────────────
front ends nox-thread-handler nox-threads (seat thread)
├ terminal client (first) ├ takes a thread's lease ├ a restricted stream per thread
├ web page (then) ├ runs the harness headless ├ the object store for large content
└ Telegram: questions, │ inside a microsandbox └ leases in the bus's key-value store
approvals ├ answers permission requests
│ │ through the mesh's asks identity seat
│ └ commits + pushes the workspace ├ persons, agents, modules, roles
│ branch every turn │ in the bus's key-value store
│ nox-workspace (node seat workspace) ├ signs logins (one key per role)
│ └ read, glob, search, edit, write, └ revokes over the bus, reads back
│ git, named tools; no command runner
│ claude-code
│ ├ mirrors a session into its thread
│ ├ rebuilds a session on pick-up
│ └ writes the login and licence token
│ into the sandbox
└──────────────── every call over the bus, carrying its thread and,
for a module acting for an agent, the agent's token
```
## 2. Threads (ADR 0383)
A **thread** is one piece of agent work, kept as a stream on the bus in the mesh's own format. The stream is the
source of truth: the conversation, the agent's questions and their answers, tool calls and results, usage, and
every change of state. Large content goes to the object store and is named from the stream. The stream is
restricted (ADR 0376): only modules granted it read it. A thread is never deleted; the trail is kept for good.
A thread is **open**, **assigned** (to a node and a harness), **released** or **stopped**. Its seat's verbs create,
assign, release, stop, move it, and take it over with a reason. The assignment is a **lease** in the bus's
key-value store, changed only by a compare-and-set on its revision. Its holder renews it every 20 s. A lease not
renewed for 60 s is said to the operator; nothing takes it over by itself.
## 3. Running a thread (ADR 0384)
The thread handler on an always-on node takes the lease and starts the harness (Claude Code today) headless, one
turn at a time. It answers the harness's permission requests through the mesh's asks, so a front end answers them in
place. At the end of every turn it commits and pushes the workspace, which is the thread's own branch on the forge;
a node that picks the thread up fetches it, so a move loses at most the turn in progress. Before a shutdown it
releases at a turn boundary; on a stop it ends the process group, since a tool's child process outlives the harness.
The claude-code module mirrors the session's stream into the thread and, on pick-up, rebuilds a session from the
thread. Usage per thread comes from each run's result and rate-limit messages on the stream, not the transcript, and the
licence manager records it with the thread as its session.
The front ends are clients of a thread doing the same work: the terminal client first, then the web page, and
Telegram for questions and approvals. Their first scope is the conversation as it streams, the agent's questions and
permission requests answered in place, sending a message and stopping a turn. The rest is tracked by issue 850.
## 4. The workspace toolkit (ADR 0385)
The node seat `workspace`, held by `nox-workspace` on every thread-handler node and served through the mesh's MCP
server, works in the thread's workspace only, by relative path: read, glob, regex search, edit, write, git, and named
tools such as running the repository's tests and its merge check. It is built early. There is no general command
runner in it, and no module offers one to an agent: a command an agent needs becomes a named tool in the module that
owns it. Claude Code's own built-in tools, its command tool included, are turned off once the toolkit is in.
## 5. Identities and roles (ADR 0386)
The identity seat, held once for the mesh, owns every **identity** (a person, an agent or a module) and every
**role**, in the bus's key-value store. An identity has a name, a kind, a role and whether it is switched on. They
change only through the seat's verbs, each recorded with who asked; anything may watch the store live. Only the
operator creates a person or an agent, or changes its role. A module's identity is asked for by the controller when
the module is assigned to a node, named by machine and module, with a role made from what its manifest declares it
calls.
A role holds **grants**. A grant is an effect everywhere, an effect on one seat, or one named tool, and may be
limited to named machines. Two fences check them: the bus, coarsely, by seat and module, from the role's signing
key; and the tool runner, exactly, tool by tool, in the SDK. Every call carries its thread; a module acting for an
agent carries a token the identity seat signed for that agent, and the tool runner then checks the agent's role. A
refusal names the missing grant; every call is recorded with its identity and thread.
## 6. Effects, asks and break-glass (ADR 0387)
Every seat verb and module tool is one permission and carries one **effect**, set in its module's manifest:
**reads**, **acts** or **dangerous**. The catalogue's check refuses an unmarked tool, once every module already in
the catalogue is marked. A dangerous tool is granted only by name, and naming one in a role goes to the operator as
an ask first. Every other dangerous act is an ask (ADR 0259), bound to that act and carried out on the warrant.
The **break-glass** agent holds every tool and never asks. It is sealed until the operator opens it, or a condition
the operator has named as opening it is raised (none is named at the start); it stays open until the operator closes
it, and every act it takes is said to the operator at once. A tool served without an effect, from a module built
before its mark, counts as dangerous.
## 7. Signed logins on the bus (ADR 0388)
The bus checks each login by its signature and reads no list. The root key is sealed in the vault and used only to
replace the identity seat's signing keys, on the operator's Approve. The seat signs with one scoped signing key per
role, so every login stays small and each identity gets its own inbox from the role's template. An agent's login
lives minutes, a module's and a node's hours; each is renewed by a reconnect forced before expiry, by the handler or
the node-engine.
A switched-off identity is revoked over the bus. The seat reads back every update, sends updates at least a second
apart, and resends the current state to every server id it has not seen on the system account, which every joined
server carries. This changes ADR 0106's rule that the bus is configured only through its settings file.
## 8. The switch (ADR 0389)
A new bus with signed logins runs beside today's, joined as a leaf node carrying the system account. Identities move
one at a time, each checked before the next. Streams, key-value buckets and the object store move last, each by a
copy of its whole configuration: copy, wait for equal counts, stop its writers, then one update hands the addresses
over. Today's bus is switched off when nothing is left on it.
## 9. Sandboxes (ADR 0390)
Each session runs in a microsandbox of its own, its version pinned and declared in a module. The workspace is
mounted; the claude-code module writes in the agent's login and the licence token without its refresh part, and
replaces the token as the licence manager refreshes it. Connections are allowed only to the vendor's interface, by
address range, and to the bus. A node whose KVM modules are not loaded gets them from the sandbox module.
## 10. The build's acceptance tests
The build is not done until each of these has been shown, and the milestones of the work breakdown (playbook 04)
carry them:
1. A licence token replaced under a running session keeps that session working.
2. The sandbox module loads KVM on the home server, and a sandbox runs there.
3. Every existing module's tools carry an effect, and the catalogue's check refuses an unmarked one.
4. The switch, run on the mesh's own modules in the lab from the bed test kept on the lab's branch
`research/053-signed-bus-logins`, loses nothing and leaves every identity on the new bus.
5. The acceptance sentence at the top, end to end.
+1
View File
@@ -57,6 +57,7 @@ document is written and this one's status becomes `implemented`.
| [`55-the-knowledge-base.md`](55-the-knowledge-base.md) | **Designed.** The mesh keeps its decision records and research efforts whole in one module, `mesh-knowledge`, on the control-node: number, status, topic, typed links both ways, each decision's clauses with their own status, and the reasoning, with every version; a record is proposed, checked at the write, reviewed by the knowledge base's own judgement and accepted there, with the operator's Approve when it needs it; agents ask what holds in one bounded call | ADR 0290 |
| [`56-the-mail-client-set-up-by-the-mesh.md`](56-the-mail-client-set-up-by-the-mesh.md) | **Designed.** On every node with a display server, the `betterbird` module installs Betterbird from a pinned upstream release mirrored into the artifact store, ships the Owl add-on pinned and installed by Betterbird's enterprise policy, and declares the operator's mail accounts in a trusted setting without credentials; each account signs in once at a person's first use, and one the profile already holds is adopted by its address | ADR 0316, ADR 0317 |
| [`57-building-config-and-provisioning.md`](57-building-config-and-provisioning.md) | **Designed.** The work of ADR 0348 and ADR 0349 as 37 packages, each an issue with its proof: foundations, template files, config in its own module, the provisioner and its client, secrets in the vault, bindings from providers, collections, a module's runtime readers, and removing the old forms; at most five worked at once | ADR 0348, ADR 0349 |
| [`58-agent-work-as-the-meshs-own.md`](58-agent-work-as-the-meshs-own.md) | **Designed.** Agent work as the mesh's own: a thread seat keeps every thread whole on the bus; a thread handler runs the harness headless in a sandbox per session, the workspace on the thread's own forge branch; a workspace toolkit and named tools, no general command runner; one identity seat for persons, agents and modules, roles of grants, every tool an effect, signed bus logins, and the switch to them one identity at a time | ADR 0383, ADR 0384, ADR 0385, ADR 0386, ADR 0387, ADR 0388, ADR 0389, ADR 0390 |
## Not yet written