Author SHA1 Message Date
jschoubben cb4b3c2398 Prove a push over SSH from g14 (issue 955) 2026-10-17 02:30:30 +02:00
12 changed files with 113 additions and 529 deletions
+14 -24
View File
@@ -98,9 +98,6 @@ and repair).
*Not:* ~~tools-sdk~~ *Not:* ~~tools-sdk~~
- **invokes** — the manifest word for the tools a module calls, `<module>.<tool>` each or `*` for every - **invokes** — the manifest word for the tools a module calls, `<module>.<tool>` each or `*` for every
one. A grant on the publish side and nothing else; a module that declares none calls nothing. one. A grant on the publish side and nothing else; a module that declares none calls nothing.
- **run for** — an assignment, or a start of a module, that serves one agent identity with that identity's machine
account, home and bus password. Only a module whose manifest declares it may be run for an identity; running for
the operator is the default run, as the operator account, with no setting (ADR 0412).
- **upgrade policy** — how a module's new builds reach its nodes: `roll` (one node first, judged, then - **upgrade policy** — how a module's new builds reach its nodes: `roll` (one node first, judged, then
the rest), `together`, or `record` (register the build, send it nowhere) the rest), `together`, or `record` (register the build, send it nowhere)
(ADR 0236). (ADR 0236).
@@ -234,7 +231,7 @@ every other domain has something to run on. **Recorded by** the controller and t
**Purpose.** To decide, send and apply what each node runs, and to say why. **Purpose.** To decide, send and apply what each node runs, and to say why.
**Recorded by** the controller's `inventory` database. **Upstream of** Provisioning, Connectivity, Data **Recorded by** the controller's `inventory` database. **Upstream of** Provisioning, Connectivity, Data
and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 0252, 0261, 0411. and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 0252, 0261.
**Uses:** node, machine, module, seat, manifest (Module), config value (Config). **Uses:** node, machine, module, seat, manifest (Module), config value (Config).
- **assignment** — a module put on a node. Its config values on that node (module-assignment config, - **assignment** — a module put on a node. Its config values on that node (module-assignment config,
@@ -267,15 +264,15 @@ and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 02
other things (see [Homonyms](#homonyms)). other things (see [Homonyms](#homonyms)).
- **apply** — the node-engine making its node match its declaration; **reconcile** is doing so again - **apply** — the node-engine making its node match its declaration; **reconcile** is doing so again
until nothing differs. until nothing differs.
- **operator account** — the machine account of the operator's person identity on a node, read from the - **operator account** — the login name of the person who works on a node, stated on the node record;
identity and no longer stated on the node record; empty for a node nobody logs into. Everything the mesh empty for a node nobody logs into. Everything the mesh places under a person's home is resolved
places under a person's home is resolved against this account's home and owned by it against this account's home and owned by it
(ADR 0181, ADR 0411). (ADR 0181).
Not a name a manifest carries. Not a name a manifest carries.
- **agent account** — the machine account of the agent identity the identity seat marks as a node's agent account, - **agent account** — the login every coding agent on a node runs as, when the node names one of its own;
on the operator's Approve, never by an assignment or a config value. Where none is marked, agents run as the stated on the node record at the controller's terminal, never by a verb or a config value. Empty means agents
operator account. It cannot run as the operator account. It cannot become root without a person, and the node-engine judges that on
become root without a person, and the node-engine judges that on every look (ADR 0266, ADR 0411). every look (ADR 0266).
- **account group** — a group of the machine's group database that a module puts an account in, by - **account group** — a group of the machine's group database that a module puts an account in, by
declaring the account with that group and nothing else of it. Several modules may each add one to the declaring the account with that group and nothing else of it. Several modules may each add one to the
same account; the mesh takes back only an account group it added same account; the mesh takes back only an account group it added
@@ -344,7 +341,7 @@ and vault (Identity and access).
**Purpose.** To issue, hold, rotate and check the credentials and grants by which modules, nodes, agents **Purpose.** To issue, hold, rotate and check the credentials and grants by which modules, nodes, agents
and the operator act. **Recorded by** the controller's `identity` and `licences` databases and the vault. and the operator act. **Recorded by** the controller's `identity` and `licences` databases and the vault.
**Upstream of** Provisioning, Change and delivery, and Operator and conversation. **Upstream of** Provisioning, Change and delivery, and Operator and conversation.
**Decided by** ADR 0085, 0113, 0183, 0225, 0234, 0386, 0387, 0388, 0411, 0413, 0414. **Decided by** ADR 0085, 0113, 0183, 0225, 0234, 0386, 0387, 0388.
**Uses:** module, node, seat, tool (Module), operator, consumer and provision (Provisioning), agent and ask (Operator and conversation). **Uses:** module, node, seat, tool (Module), operator, consumer and provision (Provisioning), agent and ask (Operator and conversation).
- **credential** — anything that proves an identity: a password, a key, a token. A **pair credential** - **credential** — anything that proves an identity: a password, a key, a token. A **pair credential**
@@ -358,13 +355,8 @@ and the operator act. **Recorded by** the controller's `identity` and `licences`
- **vault** — the module that owns every secret (`mesh-vault`, - **vault** — the module that owns every secret (`mesh-vault`,
[24 — The secrets vault](../03-DESIGN/01-to-be/24-the-secrets-vault.md)). [24 — The secrets vault](../03-DESIGN/01-to-be/24-the-secrets-vault.md)).
- **identity** — a person, an agent or a module as the mesh knows it: a name, a kind, a role, and whether it - **identity** — a person, an agent or a module as the mesh knows it: a name, a kind, a role, and whether it
is switched on; a person or an agent also has a login name, the machines it is present on and, for a person, is switched on. Every identity and role is held by the identity seat, in the bus's key-value store, and
the login kept on a machine where it differs. Every identity and role is held by the identity seat, in the changes only through its verbs (ADR 0386).
bus's key-value store, and changes only through its verbs (ADR 0386, ADR 0411). Each person and agent
identity has a bus account of its own, carrying its role (ADR 0413).
- **machine account** — an identity's login on one machine, made by the mesh from the identity: the
controller composes it and the node-engine applies it, adopting a login that exists and never renaming or
deleting one (ADR 0411).
- **role** — what an identity may call, as a set of grants (ADR 0386). Not a seat: a bare *role* is a - **role** — what an identity may call, as a set of grants (ADR 0386). Not a seat: a bare *role* is a
homonym (see [Homonyms](#homonyms)). homonym (see [Homonyms](#homonyms)).
- **grant** — one line of a role: an effect everywhere, an effect on one seat, or one named tool, and may be - **grant** — one line of a role: an effect everywhere, an effect on one seat, or one named tool, and may be
@@ -626,8 +618,8 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272, 0383, 03
line as the **controller's terminal** or as an ordinary call line as the **controller's terminal** or as an ordinary call
(ADR 0272). (ADR 0272).
- **nox** — `mesh-cli`'s alias, in interactive zsh only. A script, `sudo` and a document meant to work everywhere - **nox** — `mesh-cli`'s alias, in interactive zsh only. A script, `sudo` and a document meant to work everywhere
say `mesh-cli`. The operator also calls the mesh itself *nox mesh*. *nox* is also the agent (ADR 0414 §7): see say `mesh-cli`. The operator also calls the mesh itself *nox mesh*; on this page and in the records *nox* is the
[Homonyms](#homonyms), and say which is meant. It is the one command the operator types: every line the mesh addresses to the operator — a alias alone. It is the one command the operator types: every line the mesh addresses to the operator — a
condition's words, a verb's answer, a README, a design — names a `nox` line, never a daemon's (the controller's condition's words, a verb's answer, a README, a design — names a `nox` line, never a daemon's (the controller's
or the node-engine's binary followed by a command), which `words.py` and the catalogue's check refuse or the node-engine's binary followed by a command), which `words.py` and the catalogue's check refuse
(ADR 0272, (ADR 0272,
@@ -825,8 +817,6 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
| record | a numbered decision | **decision record** | The record | | record | a numbered decision | **decision record** | The record |
| | the literal reader of this repository, searched first for a symptom | **the record** (the `records` module) | Operator and conversation | | | the literal reader of this repository, searched first for a symptom | **the record** (the `records` module) | Operator and conversation |
| | the upgrade policy that sends nowhere | `record` | Module | | | the upgrade policy that sends nowhere | `record` | Module |
| nox | the mesh's one agent identity, its logins and accounts (ADR 0414) | **`nox`, the agent** | Identity and access |
| | the operator's terminal command, `mesh-cli` | **the `nox` command** | Operator and conversation |
| knowledge base | the module holding the decision records and research efforts whole | **knowledge base** | The record | | knowledge base | the module holding the decision records and research efforts whole | **knowledge base** | The record |
| | the predecessor's operational memory, in older playbooks (issue 430) | none: it no longer exists | — | | | the predecessor's operational memory, in older playbooks (issue 430) | none: it no longer exists | — |
| check | a pull request's two statuses | **merge check**, **merge gate**, **repository check** | Change and delivery | | check | a pull request's two statuses | **merge check**, **merge gate**, **repository check** | Change and delivery |
+4 -10
View File
@@ -12,12 +12,8 @@ A design becomes **milestones**, and each milestone becomes **issues**:
- **A milestone is usable on its own.** Once its delivery plan is done, the operator or the mesh can do - **A milestone is usable on its own.** Once its delivery plan is done, the operator or the mesh can do
something new, said in one sentence ("a Claude Code session is mirrored into a thread on the bus"). something new, said in one sentence ("a Claude Code session is mirrored into a thread on the bus").
A milestone that changes nothing anyone can use is not a milestone; fold it into the next. A milestone that changes nothing anyone can use is not a milestone; fold it into the next.
- **A milestone is one release** of playbook [07](07-feature-branches.md): prepared, developed whole, reviewed, - **A milestone is one feature step** of playbook [07](07-feature-branches.md): prepared, developed in one to
checked and delivered as one, never cut for its size or for time (the operator, 2026-10-17). two hours, reviewed, checked and delivered as one. A milestone larger than that is split.
- **Milestones are cut by release order.** A milestone ends where one repository's change must be released before
another can use it (a new manifest word before the catalogue reads it, an SDK change before its users, until
design 47's milestones 3 and 6 lift those orders); the lead checks this when cutting, so no milestone needs a
second release.
- **An issue is one repository's part of a milestone,** sized so a fixing agent finishes it inside the - **An issue is one repository's part of a milestone,** sized so a fixing agent finishes it inside the
milestone's development time. Issues that must land in order are linked `depends-on`. milestone's development time. Issues that must land in order are linked `depends-on`.
@@ -44,10 +40,8 @@ A design becomes **milestones**, and each milestone becomes **issues**:
entry in the design. entry in the design.
7. **Flip the status.** `status: in-progress`, `updated:` today; the design change goes through playbook 7. **Flip the status.** `status: in-progress`, `updated:` today; the design change goes through playbook
[08](08-small-fixes.md) (one hq pull request). [08](08-small-fixes.md) (one hq pull request).
8. **Hand each milestone to playbook 07, one at a time.** The milestone is one release of playbook 07; its issues 8. **Hand each milestone to playbook 07, one at a time.** The milestone is the feature step; its issues are the
are the release's issues; the next milestone starts when the previous one's delivery plan is done. A design waits step's issues; the next milestone starts when the previous one's delivery plan is done.
while another design's milestones are being built: one design is built at a time (playbook
[07](07-feature-branches.md)), so this step starts only when the design before it is delivered.
9. **On completion of the last milestone**, run the "when something ships" section of playbook 9. **On completion of the last milestone**, run the "when something ships" section of playbook
[02](02-graduation.md). [02](02-graduation.md).
+18 -59
View File
@@ -40,37 +40,26 @@ opened **once**, after the review. It runs in five phases, and its time is bound
| Phase | What happens | Bound | | Phase | What happens | Bound |
|---|---|---| |---|---|---|
| 0. Prepare | the lead settles the open questions and writes one context file per repository | 15 minutes | | 0. Prepare | the lead settles the open questions and writes one context file per repository | 15 minutes |
| 1. Develop | one fixing agent per repository builds every issue locally, unit tests only | the milestone's issues, 30 minutes each | | 1. Develop | one fixing agent per repository builds every issue locally, unit tests only | 1 to 2 hours |
| 2. Review | one reviewer reads all of it; reports go both ways; at most 3 rounds | 15 minutes | | 2. Review | one reviewer reads all of it; reports go both ways; at most 3 rounds | 15 minutes |
| 3. Check | push; the forge's merge checks run the integration tests; no further review | one check run | | 3. Check | push; the forge's merge checks run the integration tests; no further review | one check run |
| 4. Deliver | merge; the delivery plan builds, publishes and sends the whole feature | its delivery plan | | 4. Deliver | merge; the delivery plan builds, publishes and sends the whole feature | its delivery plan |
**One milestone is one release** (the operator, 2026-10-17). A milestone is developed whole, reviewed once, A feature that cannot be developed in about two hours is split into **steps**. Each step runs all five
pushed once and delivered once. It is never cut into steps for its size or for time: every cut adds a review, a phases on its own; the next step starts only when the previous step's delivery plan is done. A step is the
merge check, an Approve and a delivery plan of waiting, so the fewest cuts is the fastest. Milestones are cut by unit that ships, so nothing finished waits for work that is not.
release order when the design is broken up (playbook [04](04-build-handoff.md)); a release-order dependency found
inside a milestone is a fault of that cut, said to the operator, never a silent extra step.
**One design is built at a time** (the operator, 2026-10-17). While the milestones of one design are being **Scope is fixed at phase 0.** An issue found during a feature goes to the next step or the next feature,
built, no milestone of another design starts: its issues stay `open`, and nobody moves them to `diagnosing` or
`fixing`. The next design starts when the last milestone of the one being built has its delivery plan done. A
lead who finds another design's milestone already in `fixing` asks its owner before starting its own. A small
fix ([playbook 08](08-small-fixes.md)) and an issue that is in no design's milestones are not held by this rule.
**Scope is fixed at phase 0.** An issue found during a feature goes to the next milestone or the next feature,
never into the running one, unless it blocks the running one. never into the running one, unless it blocks the running one.
## Phase 0 — Prepare (the lead, larger model) ## Phase 0 — Prepare (the lead, larger model)
1. **Name the feature once:** `feat/<slug>`, the same branch name in every repository it touches. The 1. **Name the feature once:** `feat/<slug>`, the same branch name in every repository it touches. The
branch is never protected. **One milestone is one branch per repository** (the operator, 2026-10-17), with one branch is never protected.
folder `.work/<slug>/`; never a second name for part of it (`-2`, `-step2`).
2. **Make the worktrees:** one per touched repository under `.work/<slug>/<repo>`, branched off `main` 2. **Make the worktrees:** one per touched repository under `.work/<slug>/<repo>`, branched off `main`
(`git worktree add .work/<slug>/<repo> -b feat/<slug> origin/main`). The lab beds find the other (`git worktree add .work/<slug>/<repo> -b feat/<slug> origin/main`). The lab beds find the other
repositories by sibling path, so add the untouched siblings the lab reads as detached worktrees on repositories by sibling path, so add the untouched siblings the lab reads as detached worktrees on
`main` (`git worktree add --detach .work/<slug>/mesh-tools main`), never symlinks. **A repository's own `main` (`git worktree add --detach .work/<slug>/mesh-tools main`), never symlinks.
clone stays on `main`** (the operator, 2026-10-17): no work is done in it and no branch is checked out
there, so a session that opens in it reads what holds now; fetch and fast-forward it before reading.
3. **Settle every open question** in the feature's issues before any agent starts: on the issue, through 3. **Settle every open question** in the feature's issues before any agent starts: on the issue, through
the knowledge base, or by asking the operator. A fixing agent that meets an undecided design question the knowledge base, or by asking the operator. A fixing agent that meets an undecided design question
stops, and its run is spent again. stops, and its run is spent again.
@@ -144,21 +133,21 @@ never into the running one, unless it blocks the running one.
second review: phase 2 reviewed this tree. second review: phase 2 reviewed this tree.
3. **A check that fails** goes back to the reviewer with the failure, for **one more cycle** of phase 2 with 3. **A check that fails** goes back to the reviewer with the failure, for **one more cycle** of phase 2 with
the same fixing agents; then push once again. the same fixing agents; then push once again.
4. **The milestone's acceptance is proven before it is marked ready.** A milestone that adds or changes a module 4. **The step's acceptance is proven before it is marked ready.** A step that adds or changes a module
carries an integration test that starts the module whole, its process against the real bus and store, and carries an integration test that starts the module whole, its process against the real bus and store, and
shows the milestone's acceptance sentence; the forge's checks run it (ADR 0391 §1, §6). Unit tests alone are not shows the step's acceptance sentence; the forge's checks run it (ADR 0391 §1, §6). Unit tests alone are not
that proof; the mesh has no end-to-end tests yet, and the lab is not used (the operator, 2026-10-16). that proof; the mesh has no end-to-end tests yet, and the lab is not used (the operator, 2026-10-16).
5. **The reviewer marks each pull request ready as soon as the acceptance is shown** with 5. **The reviewer marks each pull request ready as soon as the acceptance is shown** with
`mesh-delivery.mark-ready` (the delivery of the head it approved, who reviewed it and why), which writes the `mesh-delivery.mark-ready` (the delivery of the head it approved, who reviewed it and why), which writes the
label `mesh/ready` and then `ready at <full sha>`, the order the forge counts (issue 913); mesh-delivery merges it when its checks pass label `mesh/ready` and then `ready at <full sha>`, the order the forge counts (issue 913); mesh-delivery merges it when its checks pass
and the operator approved the merge into main on Telegram, the channel that proves who answered today (ADR 0364, and the operator approved the merge into main on Telegram, the channel that proves who answered today (ADR 0364,
ADR 0398 §3; an Approve at the terminal is issue 953). Beyond that ADR 0398 §3; an Approve at the terminal is issue 953). Beyond that
Approve, the operator's word is waited for only when the milestone changes what the operator sees, removes Approve, the operator's word is waited for only when the step changes what the operator sees, removes
data, or needs a step only the operator's terminal can take; the lead says which before the milestone starts. data, or needs a step only the operator's terminal can take; the lead says which before the step starts.
**One exception: a catalogue change that uses a new manifest word.** The catalogue's merge check reads **One exception: a catalogue change that uses a new manifest word.** The catalogue's merge check reads
manifests with the controller the mesh runs, so the controller's pull request is merged and delivered first; manifests with the controller the mesh runs, so the controller's pull request is merged and delivered first;
then `mesh-delivery.recheck` the catalogue's and merge it on the new verdict. Such work is two releases, the controller's first, and playbook 04 cuts its milestones that way. then `mesh-delivery.recheck` the catalogue's and merge it on the new verdict. Plan such a feature as two steps.
The exception ends when milestone 3 of design 47 is delivered (issue 977, ADR 0402, ADR 0403): the group's composed The exception ends when milestone 3 of design 47 is delivered (issue 977, ADR 0402, ADR 0403): the group's composed
check, judged by the group's controller, is then each member's gate, and the feature is one group. check, judged by the group's controller, is then each member's gate, and the feature is one group.
@@ -207,7 +196,7 @@ unit and its bound or baseline; an issue is named by its number and a few words,
waits for, marked ✓ landed, ▶ fixing, ⏸ waiting, ✗ stopped: waits for, marked ✓ landed, ▶ fixing, ⏸ waiting, ✗ stopped:
``` ```
Feature <slug> · milestone <n> of <m> · phase <name>, <minutes used> of <bound> min Feature <slug> · step <n> of <m> · phase <name>, <minutes used> of <bound> min
Issues: <landed> of <total> landed · <fixing> fixing · <waiting> waiting Issues: <landed> of <total> landed · <fixing> fixing · <waiting> waiting
mesh-controller (agent: fixing, 2 of 3 landed) mesh-controller (agent: fixing, 2 of 3 landed)
@@ -228,11 +217,11 @@ Next: <the next action, one line>
**The short layout** (on a channel such as Telegram, or when nothing changed but time): **The short layout** (on a channel such as Telegram, or when nothing changed but time):
``` ```
<slug> milestone <n>/<m> · <phase> <used>/<bound> min · <landed>/<total> landed · past bound: <none | what> · needs you: <nothing | what> · week <n> % <slug> step <n>/<m> · <phase> <used>/<bound> min · <landed>/<total> landed · past bound: <none | what> · needs you: <nothing | what> · week <n> %
``` ```
A report says what changed since the last one first. In the session every report is the full layout, the whole A report says what changed since the last one first. In the session every report is the full layout, the whole
tree of the milestone's issues included, also when only time passed: the operator reads the state from it without tree of the step's issues included, also when only time passed: the operator reads the state from it without
asking. The short layout is for a channel. asking. The short layout is for a channel.
## The context of the lead's own session ## The context of the lead's own session
@@ -242,28 +231,9 @@ lead prepares a `/clear` as the mesh's instruction "prepare before clear" says:
feature's phase, its branches and heads, the context files' paths, the agents' ids, the wait list and the feature's phase, its branches and heads, the context files' paths, the agents' ids, the wait list and the
next action; then the prompt the operator pastes. The context files make the feature resumable after a clear. next action; then the prompt the operator pastes. The context files make the feature resumable after a clear.
## The design's state is written back
Other sessions know what is built only from the design's frontmatter and the issue tracker; what the lead
knows and does not write back, nobody else knows. A design merged in one delivery with three of its tasks
left, its frontmatter still saying `designed`, read to another session as not built, and a second design
was started beside it (issue 1009, 2026-10-17). So the lead writes the design's state back at each of these
moments, the same hour, an hq change going through [playbook 08](08-small-fixes.md):
- **The first milestone starts:** the design's `status: in-progress` ([playbook 04](04-build-handoff.md) step 7),
if it is not already.
- **A milestone's delivery plan is done:** its issues are resolved (phase 4), and the design's milestone entry says
it is delivered, with the date.
- **The building pauses with work left** (another design takes its turn, a hand-over, the session's end): the
design stays `in-progress`, and each issue left gets a note with `issue-tracker.note` saying what it waits
for and what comes after it ("built after design N is delivered, before design M"). The first issue of
a design that waits its turn gets the same note.
- **The last milestone is delivered:** [playbook 02](02-graduation.md), "When something ships": the as-is is
written and the design says `implemented`.
## Issues that stop ## Issues that stop
An issue stays in `fixing` only while an agent works on it. A milestone that stops, for a wait past its bound, a An issue stays in `fixing` only while an agent works on it. A step that stops, for a wait past its bound, a
hand-over, or the session's end, moves each of its issues not delivered back to `open` with hand-over, or the session's end, moves each of its issues not delivered back to `open` with
`issue-tracker.move` and a note saying how far it got (its branch, its last commit, what is left). A list of `issue-tracker.move` and a note saying how far it got (its branch, its last commit, what is left). A list of
issues in `fixing` that nobody works on reads to the operator as stuck work. issues in `fixing` that nobody works on reads to the operator as stuck work.
@@ -274,25 +244,14 @@ issues in `fixing` that nobody works on reads to the operator as stuck work.
request into a feature branch exists. A pull request per issue, or an agent waiting on a merge check in request into a feature branch exists. A pull request per issue, or an agent waiting on a merge check in
phase 1, is the failure this playbook prevents. phase 1, is the failure this playbook prevents.
- A context file exists per touched repository under `.work/<slug>/` while the feature runs. - A context file exists per touched repository under `.work/<slug>/` while the feature runs.
- Every repository's own clone says `main` (`git -C <clone> branch --show-current`); another branch there is
work done outside a worktree (issue 1010).
- After a feature's delivery, `git branch -r | grep feat/<slug>` and `git worktree list` return nothing for it. - After a feature's delivery, `git branch -r | grep feat/<slug>` and `git worktree list` return nothing for it.
- A design whose milestone issues are partly resolved says `in-progress`, and one whose issues are all - More than one open pull request in a repository that share no feature name, or a feature step whose issues
resolved and delivered says `implemented`: read its Milestones section against `issue-tracker.get` for each
issue. A design that says `designed` with a resolved milestone issue is a state not written back.
- A milestone whose issues are all resolved and delivered has the date in its entry in the design's Milestones
section; an entry without it is a delivery not written back.
- Every open issue of a design that says `in-progress` and has no agent on it shows, in `issue-tracker.get`, a
note saying what it waits for; one without such a note is a pause not written back.
- The milestone issues in `fixing` (`issue-tracker.list`) belong to one design at most: milestone issues
of two designs in `fixing` at once is the failure of "one design is built at a time".
- More than one open pull request in a repository that share no feature name, or a milestone whose issues
stay in `fixing` for more than a working day, is the smell: stop and bring it to the operator. stay in `fixing` for more than a working day, is the smell: stop and bring it to the operator.
- A review report that asks a change outside the issues' done-when, or a fourth round, is the failure of the - A review report that asks a change outside the issues' done-when, or a fourth round, is the failure of the
review rules; `issue-tracker.list` with `flag: abandoned` lists issues left in `fixing` with nobody on them. review rules; `issue-tracker.list` with `flag: abandoned` lists issues left in `fixing` with nobody on them.
- A pull request reviewed and green but not marked ready for more than 15 minutes is a lead that did not - A pull request reviewed and green but not marked ready for more than 15 minutes is a lead that did not
mark it. mark it.
- A milestone that adds or changes a module marked ready with no integration test that starts the module whole is - A step that adds or changes a module marked ready with no integration test that starts the module whole is
the failure of phase 3's fourth step. the failure of phase 3's fourth step.
- A bed run from `.work/<slug>/mesh-lab` that fails naming a `.work/<slug>/<repo>/…` path it cannot find is a - A bed run from `.work/<slug>/mesh-lab` that fails naming a `.work/<slug>/<repo>/…` path it cannot find is a
missing sibling worktree, not a mesh fault. missing sibling worktree, not a mesh fault.
+1 -4
View File
@@ -29,8 +29,7 @@ one delivery**. Everything before the push is local and fast.
need a second repository, or more than about an hour, stops here and becomes a feature; one that needs a need a second repository, or more than about an hour, stops here and becomes a feature; one that needs a
decision stays a small fix. decision stays a small fix.
2. **Move the issue to `fixing`** with `by` as who@node. 2. **Move the issue to `fixing`** with `by` as who@node.
3. **Build on a local branch** `fix/<issue>-<slug>`, in a worktree of its own branched from `main`, never in 3. **Build on a local branch** `fix/<issue>-<slug>`, in a worktree of its own branched from `main`. Write the
the repository's own clone, which stays on `main` (playbook [07](07-feature-branches.md), phase 0 step 2). Write the
test that fails today first; make the change; keep `gofmt`, `go vet` and the unit tests of what changed green test that fails today first; make the change; keep `gofmt`, `go vet` and the unit tests of what changed green
(each well under a second; never a merge check or a database locally: how-we-build §5, ADR 0391). (each well under a second; never a merge check or a database locally: how-we-build §5, ADR 0391).
Commit, naming the issue. For a playbook or design edit, run the repository's own checks (in hq, Commit, naming the issue. For a playbook or design edit, run the repository's own checks (in hq,
@@ -100,5 +99,3 @@ reading itself and uses one subagent only for the review.
asking a change outside the issue's done-when, is the failure of steps 4 to 6. asking a change outside the issue's done-when, is the failure of steps 4 to 6.
- An issue in `fixing` with nobody on it shows in `issue-tracker.list` with `flag: abandoned`; a stopped fix - An issue in `fixing` with nobody on it shows in `issue-tracker.list` with `flag: abandoned`; a stopped fix
moves it back to `open` with its note. moves it back to `open` with its note.
- The repository's own clone says `main` (`git -C <clone> branch --show-current`); another branch there is a
fix built outside a worktree (issue 1010).
+47 -122
View File
@@ -1,144 +1,69 @@
--- ---
layer: as-is layer: as-is
status: implemented status: implemented
code: [mesh-tools, mesh-host, mesh-controller] code: [mesh-catalog modules/mesh-console, mesh-tools src/mesh.ts, mesh-tools src/http.ts, mesh-tools src/runtime.ts, mesh-controller internal/broker, mesh-controller cmd/mesh-controller/check.go, mesh-controller cmd/mesh-controller/seatverbs.go]
updated: 2026-10-17 updated: 2026-10-10
decisions: decisions:
- "ADR 0152" - "ADR 0152"
- "ADR 0175" - "ADR 0095"
- "ADR 0195" - "ADR 0037"
- "ADR 0197"
- "ADR 0367"
- "ADR 0154" - "ADR 0154"
--- ---
# The mesh MCP server, as it runs # The mesh MCP server, as it runs
**The `mesh-console` container no longer exists.** Read 2026-10-17 from `mesh-tools` (package **The mesh's tools reach a person through a module the mesh assigned to their machine.** Since
`node-tools`), `mesh-host` and `mesh-controller` on their forges' main, and from the live mesh 2026-09-30 a workstation that is a node can be assigned `mesh-console`; the mesh mints a bus account
through `mesh_overview`, `mesh_machine`, `mesh_runtimes` and `mesh_describe`. Checked: the `<node>.mesh-console`, seals its credential to the machine, and the container binds
catalogue (`mesh-catalog`) carries no module named `mesh-console` any more — the name survives only `127.0.0.1:<port>` with the port the mesh assigned for the manifest's declared one. An agent on the
in the glossary's retired-words list and in a test fixture — and none of the four nodes answers a machine is pointed at `http://127.0.0.1:<port>/mcp` and sees the mesh's tools; a person uses the same
module of that name. endpoint. Nothing on the machine holds a credential a person had to carry.
## What it is now
**The tool runner is one of the mesh's core components**, named `node-tools` beside
`mesh-controller`, `mesh-host` (the node-engine) and `nats` (the bus) — the mesh's own repositories'
check treats the four the same way, as the core whose health the mesh itself defines rather than a
module's own. `mesh-host` and `mesh-controller` supervise, compose and account for it as a core
process rather than as an ordinary assignment (`ComponentNodeTools`/`BusNodeTools` in both, the
witness and lease code that restarts and rolls it back, the account named `<node>.node-tools` on
the bus).
**It still carries a manifest, `node-tools/module.json` in `mesh-tools`,** and that manifest is
where its grants live: `invokes` names `*`, so its account may call every tool on the mesh;
`waits` names the five event families `mesh_wait` may subscribe to (the knowledge base's reviews,
mesh-delivery's transitions, the forge's status, the controller's checked event, and the operator
channel's decision for the agent module) — the bus refuses a subscription outside this list
(ADR 0367 §2); and it
`provides` one provision, the MCP endpoint, at node scope, and `listens` on the port it serves from,
opened only to the machine itself. Its code lives under `node-tools/` in the `mesh-tools`
repository (`internal/console`, `internal/announce`, `internal/bus`).
**One runtime per node, run by the node-engine as a sibling process, never a container**
(ADR 0175). It holds the node's one bus credential and may call every tool on the mesh; its account
publishes nothing else. It binds `127.0.0.1:<port>` and refuses to bind anything else, said before
binding rather than after (`node-tools/internal/console/http.go`, citing ADR 0152): whoever is on
the machine owns the mesh there, and nobody else may reach this port. An agent or a person on the
node is pointed at `http://127.0.0.1:<port>/mcp` once; the `serverInfo.name` the handshake answers
is `mesh`.
## What it answers ## What it answers
`initialize`, `notifications/initialized`, `ping`, `tools/list`, `tools/call`, over HTTP, one JSON `initialize`, `tools/list`, `tools/call`, over HTTP, one JSON body per request, no session and no event
body per request, no session and no event stream (`node-tools/internal/console/mcp.go`). stream. `tools/list` is what the running modules answered: every tool runner built on or after that day
serves a `tools` verb for its module, and the mesh MCP server asks the catalogue for the roster and each module
for its tools. A module that did not answer is named in the list's `_meta.notAnswering`. On the day it
shipped that was 36 of 51 modules — those that serve no tools at all, and those whose rebuilt runtime the
mesh records rather than rolls out — and 62 tools from the rest.
**It announces seven tools, not five** (ADR 0195, extended by ADR 0367): `mesh_overview`, `tools/call` reaches any tool by `<module>.<tool>`, listed or not. The mesh MCP server's grant is `*`, so what it
`mesh_machine`, `mesh_search`, `mesh_describe`, `mesh_call`, `mesh_runtimes` and `mesh_wait`. The may call is every tool on the mesh; its account may publish nothing else and subscribes nothing.
handshake's `instructions` text names all seven and the four address forms. `mesh_overview` is
where the mesh-wide count of modules and tools is read live — a number copied into this document
would drift; asked on 2026-10-17 it answered 103 modules and 645 tools across the whole mesh, with
two module instances (`route-proxy` on two of the nodes) named as not answering rather than silently
absent.
**Addresses, exactly as the handshake states them:** `<seat>.<verb>` for a seat held once for the No call is refused for want of a person's approval: a tool runner serves every call it is issued, whoever made
mesh (`mesh-controller.status`); `<node>/<seat>.<verb>` for a seat every machine holds it, and cannot tell an agent from the operator (ADR 0259 §6, issue 340). An act waits for the operator's warrant
(`<node>/node-packet-filter.rules`); `<node>/<module>.<tool>` for a module assigned to one machine; only where its module asks first, in its own code: the agent module's instruction proposals, and the
and `<module>.<tool>` also for a module whose instances are interchangeable. A tool not listed can controller's settings proposals and the actions it offers on a condition.
still be called by address — listing is discovery, calling is the grant — and a module that is not
interchangeable is refused with the machines it runs on rather than guessed at.
**What exists is what announced itself** (ADR 0197). Every node's tool runner answers the NATS services
protocol's `$SRV.PING`, `$SRV.INFO` and `$SRV.STATS`, one service per process, naming in each
endpoint's metadata which module, seat, scope and machine serves it. `mesh_overview` and
`mesh_machine` gather one request's answers: at least 750ms so a runtime that only just answered
`$SRV.PING` is still waited for, up to 5s for one that said it is there but has not yet sent its
full announcement — a fixed shorter window lost the largest runtime's listing once a live
measurement found how slow its round trip over the bus could be. An announcement unchanged since
last asked (by its digest) is not re-fetched, which is why `mesh_runtimes` can show a runtime
answering in under 100ms for hundreds of kilobytes of discovery.
## mesh_wait
**The seventh tool, added by ADR 0367**, lets a session wait on the bus for an event instead of
polling: a call names one or more granted event families and an optional filter, and either answers
the first match or says it timed out. The tool runner's own bus account hears only the families its
manifest's `waits` list names; an event it hears is never taken as the state — a session reads the
state from the verb that owns it once woken. It is meant to be called in a loop from a background
command, not inline, because one call blocks until it answers or times out.
## The mesh's own verbs ## The mesh's own verbs
**These already answer directly, not as a stated future.** Beside every assigned module's tools, *Since 2026-09-30 evening (ADR 0154).*
the tool runner asks the `mesh-controller` seat for its own verbs the same way (ADR 0154): measured The mesh MCP server asks the `mesh-controller` seat's `tools` verb beside the modules and lists every role's
live, the mesh-controller runtime is one instance, mesh-wide, and it answers 63 verbs for that one tools as `<seat>.<verb>` — `mesh-controller.status`, `mesh-controller.push` and the other ten. A call
seat (`status`, `push`, `assign`, `build`, `artifacts` and the rest), and the `mesh-controller` to `<prefix>.<name>` reaches the seat when the prefix is a seat declaring that verb, and the module
*module* answers none of its own — it serves no module tools, only its seat's, exactly as design 33 otherwise; `seat:<seat>.<verb>` says so outright. When the controller does not answer, the list
described while it was still only designed. The 56-to-93 seat-verb counts each node's tool runner names `mesh-controller (seat)` as not answering and carries the modules' tools regardless. The
answers (below) are a different thing: the tool runner serves the verbs of whichever node-scoped `mesh-controller` *module* is always named as not answering: it serves no module tools, only its seat's.
seats are held on that node (`node-backup`, `node-build-agent`, `node-service-manager` among them),
on behalf of the modules holding them, not the mesh-controller seat's — that one is served by the
mesh-controller runtime itself, a separate process, and the tool runner only asks it and relays what
it answers.
## Where it runs ## Around it
**Every node, not a per-operator assignment.** Measured live: all four of the mesh's machines - **`invokes`** in a manifest is the grant. It is composed into the bus's user list exactly as a
answer a `node-tools` runtime, each with its own module and tool count (between 294 and 312 tools, person's account is; the mesh MCP server is the only module that declares it.
56 to 93 seat verbs, in this reading). This is a fact that shipped past the design it came from: the - **`module check <file|dir>…`** on the controller's binary judges a manifest with no mesh: the strict
to-be document still describes it as assigned to "whichever machines an operator sits at"; what parse, every per-manifest problem, and the rules between the manifests given. It prints what it cannot
actually runs is the opposite — every node the mesh holds carries one, because it is core judge without a store rather than refusing. The mesh MCP server's own manifest was the first thing checked
infrastructure the mesh itself depends on (discovery, the controller's own verbs, every module's with it, and the whole catalogue passes.
tools), not a convenience assigned to a workstation. There is accordingly no assign or unassign verb - **The person's client remains.** `operator issue` and `mesh tools|call|mcp` with a credential file
for it: removing it from a node is not an operation the controller or the catalogue exposes. still work, for a machine that is not a node and for a mesh not yet able to assign anything.
`mesh tools --console <url>` goes through a running mesh MCP server with no credential; it is covered by the
runtime repository's tests and was not exercised on the live mesh.
## What this repository's prior text got wrong ## What shipped bent
The document this replaces described `mesh-console` as a catalogue module served by a container and - A module registered by hand from the catalogue with `--source <url> --path modules/<m>` records a URL,
assigned only to workstations. That module and its container are gone; the manifest is not — it not a place on the git seat: `--self` takes the forge path form (`<owner>/<repository>`), which the
moved to `node-tools/module.json`, still declares `invokes: ["*"]`, and gained `waits` and the operator did not pass. The rebuild-on-merge matched the URL anyway.
`mcp-endpoint` provision, described above. What also moved is which language builds it: the - Modules whose upgrade policy is *record* — the forge among them — answered `tools` only once
manifest's `build.artifacts` names a Go binary built from `cmd/node-tools`, and that is what a node something pushed their rebuilt runtime; until then they are listed as not answering while still
runs. The repository still carries the earlier TypeScript sources (`node-tools/src/mesh.ts`, callable. That is the policy doing what it says, not a fault of the mesh MCP server.
`http.ts`, `runtime.ts`, `announce.ts`, `broker-nats.ts`, `client.ts`, `launch.ts`, `main.ts`,
`mcp.ts`) on main, but the manifest does not build from them. The quirks the prior text recorded
from the `mesh-console` era (a module registered by hand recording its source as a URL; a
*record*-policy module staying listed as not-answering until its rebuilt runtime is pushed) are not
re-checked here: they were about the `mesh-console` container's registration path, which no longer
exists.
## How it is checked
- `mesh_overview` and `mesh_machine <node>`: the live tool and seat-verb counts, and which modules
are named not-answering.
- `mesh_runtimes`: which runtimes answered discovery, how long they took, how large their
announcement was, and whether it was shortened to fit the bus — read this when an address is said
to be missing or on another machine.
- `mesh_describe <address>` for one tool's schema; a call through `mesh_call` reaches the module or
seat over the bus and the answer is its own, unshaped.
- The code: `mesh-tools` (`node-tools/internal/console`, `internal/announce`, `internal/bus`),
`mesh-host` and `mesh-controller`'s core-component handling of `node-tools` (witness, lease,
bus-user naming). Their unit tests hold the loopback-only refusal, the seven-tool handshake, the
discovery gather window and `mesh_wait`'s event-then-read-state rule.
- `knowledge-base.get` with `kind: decision` and number 152, 175, 195, 197, 367 or 154.
@@ -1,116 +0,0 @@
---
layer: as-is
status: implemented
code: [mesh-controller, mesh-catalog modules/claude-code, mesh-catalog modules/gitea, mesh-catalog modules/ssh-client]
updated: 2026-10-17
decisions:
- "ADR 0266"
- "ADR 0399"
- "ADR 0409"
- "ADR 0410"
---
# Accounts on a machine, as they run
**A node record names at most two logins: the operator account, always, and an agent account, on one node of
four. Where a node names no agent account, agents run as the operator account. Nothing ties an identity to a
login, and a module that needs one declares it itself.** Read on 2026-10-17 from the controller's and the
catalogue's code on main (both local clones were a few commits behind the forge, so each claim below was
also checked against a live read) and from the live node records. The change planned in ADR 0411 to 0414
replaces this document when it is built.
## The node record
- **The operator account is stated at the controller's terminal** with `node account <node> <account> [home]`.
It is the person's login on that machine. The controller refuses to set it to the node's agent account.
- **The agent account is stated at the controller's terminal only** with `node agent-account <node> <account>
[home]`, and cleared with `--clear`. It is no verb and no setting, so an agent cannot name itself one
(ADR 0266 §1). The controller refuses root, a malformed login, a well-known service account and the
operator account.
- **Five machine facts come from the record.** `account` and `account-home` are the operator's, absent when
the node has no operator account. `agent-account` is the agent account, or the operator account where none is
named; `agent-home` is its home (`/home/<account>` when no home is stated); `agent-root` is `never` only where
the node names an agent account of its own, otherwise empty. Modules read them as `${machine:...}` words.
- **One node of four names an agent account today**, the control node. The workstation, the laptop and the
home server name none: `mesh-controller.node` says there "agents run as the operator account; no agent
account is named", and the plan for the workstation declares the `claude-code` account resource with the
operator's name and no `root` field. The tool runner also receives `MESH_AGENT_ACCOUNT` and `MESH_AGENT_HOME`.
- **Not verified:** whether the mesh-config snapshot carries either account. The controller's code reads both
from its own node table (columns for account, account home, agent account and agent home); no second place
that states them was found.
## What claude-code does with the agent account
- **It declares a `user` resource** named `${machine:agent-account}` with home `${machine:agent-home}` and
`root: ${machine:agent-root}`, and a `~/.claude` directory under that home owned by it, mode 0700. On the
control node the plan holds the resource with `root: never`; elsewhere it holds the operator's account.
Where the account is new, the mesh creates it with a home, its own group and no password (ADR 0266 §2).
- **`claude-agent`** is a file the module writes at `/usr/local/bin/claude-agent`. Run as the agent account
it starts `claude`; run as any other account it runs `sudo -iu <agent account> claude` through that
account's own sudo, carrying the operator's guard override only if it is set in that shell. The file
`/etc/claude-code/agent-account` holds the account's name.
- **The guard of ADR 0266 §28** is the module's hook on every tool call. On a node with an agent account
of its own it refuses each call of a session that runs as another account, with no override, and tells the
session to start with `claude-agent`. Its record goes to the machine's journal. The same guard also refuses
ssh to a mesh machine and writes to `/etc/hosts` and the resolver file (ADR 0245).
- **Not verified live:** this rests on the guard's code and README, not on a refusal seen on a running machine.
## The judgement that the agent account cannot become root
- **The node-engine judges `root: never` on every look**, by reading only: uid 0, a group that grants root,
a sudo, doas or polkit rule, a container runtime's socket or API port, a secret the mesh placed for another
account, a setuid program no package owns, and kernel link protection off (ADR 0266 §12 to §16). A way found,
or a question unanswered, makes the account resource unhealthy. It does not prevent any way to root.
- **The controller's self-check holds** that a node naming an agent account has it judged, in a verdict heard
within 15 minutes, as unable to become root (ADR 0266 §21 to §22). Otherwise it raises the urgent condition
`agent-can-become-root`. `mesh-controller.node` shows the verdict: for the control node it reads "cannot
become root without a person", judged the same day.
- **On the three nodes without an agent account** the check does not apply. The controller instead holds an
urgent condition `root-not-free` on each machine that runs reads or the mail notifier. Two of the three are
silenced until 2026-10-23 on the operator's decision; the home server's is open and not silenced.
## The accounts agents push as on the forge
- **Today the forge has a user per machine**, made by the gitea module's `forge-account` provision and
holding that machine's key, plus a write token the module still mints for each (ADR 0398, ADR 0400). ADR 0410
decides one forge user, `nox-agent`, in the agents' write team, each machine's key titled by machine. No tool
reads the forge's user list, so whether `nox-agent` exists yet is **not verified**.
- **The forge key is meant for the agent account** (ADR 0409 §2), but a module cannot run as that account:
the controller refuses it (issue 989, open). So the `ssh-client` module's key keeper still runs as the
operator account.
- **An ordering deadlock** between `ssh-client`, which wants `forge-account`, and `gitea`, which provides it,
stopped every node's declaration from composing on 2026-10-17 at 00:35 until gitea was registered three
minutes later (issue 986). The four urgent conditions it raised were not observed again but stay raised,
because the controller's declaration check times out and never clears them (issue 992).
Git over ssh was proven from the laptop only; the proof on the other three, and retiring the forge token the
module still places, wait on issues 989 and 991 (the latter in `fixing`).
- **Main stays closed**: a push to the main branch is refused, and a merge waits for a person (ADR 0398).
## Logins on the bus
Not here: see [`16-logins-on-the-bus.md`](16-logins-on-the-bus.md).
## What it cannot do
- **No identity carries a login.** An agent, a person and a module are not tied to an account on a machine;
the node record names a login per node and nothing else.
- **A module declares a login itself.** Each module that needs an account writes a `user` resource of its own,
and nothing checks that two modules agree or that a login exists on every machine that needs it.
- **Agent and operator share a login on three of four machines.** There the agent runs as the operator account,
which may become root; the guard's account rule does not apply, and the exposure is a held condition, not a fix.
- **No per-agent bus password.** Every session on a machine calls through that machine's tool-runner login
(document 16), so one agent's calls cannot be told from another's.
- **Detects, does not prevent.** The judgement above lists the ways to root it looks for; a way it does not list
is not seen (ADR 0266 §17).
## How it is checked
- `mesh-controller.node` with a node name: "agents run as" line, the verdict and when it was judged, the open
conditions (`agent-can-become-root`, `root-not-free`, `uncomposable`).
- `mesh-controller.plan` with a node name: the `claude-code.agent-account` resource and its `root` field; with
`files` set, the files given, including `claude-agent` and `agent-account`.
- `mesh-controller.conditions` for the held conditions; `claude_code_guard` for the guard's record, `claude-code`
tools for its status.
- `knowledge-base.get` with `kind` decision and number 266, 409 or 410; `issue-tracker.get` with 989 or 991.
- The code: the controller's node-record and agent-account files, and the `claude-code` module's manifest and
guard in the catalogue. Their unit tests hold the node record's refusals and the guard's account rule.
-1
View File
@@ -25,7 +25,6 @@ Where the two disagree, the implementation wins and the disagreement is stated.
| [`14-a-modules-state.md`](14-a-modules-state.md) | A module's current state on the bus: what the controller creates, the runtime serves, and the first live use showed | | [`14-a-modules-state.md`](14-a-modules-state.md) | A module's current state on the bus: what the controller creates, the runtime serves, and the first live use showed |
| [`15-the-agent-and-its-licences.md`](15-the-agent-and-its-licences.md) | The operator's agent on every machine and the licence manager that keeps its licences | | [`15-the-agent-and-its-licences.md`](15-the-agent-and-its-licences.md) | The operator's agent on every machine and the licence manager that keeps its licences |
| [`16-logins-on-the-bus.md`](16-logins-on-the-bus.md) | The bus's one list of logins, a login per module assignment, persons limited to their tools, sessions calling through the tool runner's login, and no effect on any tool | | [`16-logins-on-the-bus.md`](16-logins-on-the-bus.md) | The bus's one list of logins, a login per module assignment, persons limited to their tools, sessions calling through the tool runner's login, and no effect on any tool |
| [`17-accounts-on-a-machine.md`](17-accounts-on-a-machine.md) | The operator and agent accounts on the node record, claude-code's own login and guard, the judgement that the agent account cannot become root, and the forge accounts agents push as |
## What these documents are not ## What these documents are not
+8 -15
View File
@@ -1,13 +1,11 @@
--- ---
layer: to-be layer: to-be
status: implemented status: designed
code: [mesh-tools, mesh-host, mesh-controller] code: [mesh-catalog, mesh-tools, mesh-controller]
updated: 2026-10-17 updated: 2026-10-03
decisions: decisions:
- "ADR 0197" - "ADR 0197"
- "ADR 0195" - "ADR 0195"
- "ADR 0175"
- "ADR 0367"
- "ADR 0160" - "ADR 0160"
- "ADR 0159" - "ADR 0159"
- "ADR 0152" - "ADR 0152"
@@ -165,16 +163,11 @@ matched it by URL.
## What this does not settle ## What this does not settle
- **The mesh's own verbs on the bus.** Design 33's third family: these now answer directly, through - Narrowing a mesh MCP server's grant per assignment. ADR 0046 makes it a setting; nothing reads one yet.
the `mesh-controller` seat, under ADR 0154 — settled, not open. [As-is 13](../00-as-is/13-the-console.md) - The mesh's own verbs on the bus. Design 33's third family; this document only says where they appear
records it live. once they exist.
- **Narrowing the tool runner's grant per assignment.** ADR 0046 makes it a setting; nothing reads - A person's identity behind the mesh MCP server. The mesh sees the mesh MCP server's account; design 15 keeps the
one yet. Owned by [design 58](58-agent-work-as-the-meshs-own.md) milestone 6 (issue 883): an question open.
identity asked for each assignment is one of that milestone's parts.
- **A person's identity behind the mesh MCP server.** The mesh still sees only the tool runner's own
account, never the caller's; design 15 raised the question and leaves it open. Owned by the same
milestone (design 58 milestone 6, issue 883): its outcome is that every person, agent and module
becomes an identity with a role, and a call carries which one made it.
## References ## References
@@ -2,10 +2,8 @@
layer: to-be layer: to-be
status: in-progress status: in-progress
code: [mesh-catalog modules/claude-code] code: [mesh-catalog modules/claude-code]
updated: 2026-10-17 updated: 2026-10-13
decisions: decisions:
- "ADR 0411"
- "ADR 0412"
- "ADR 0343" - "ADR 0343"
- "ADR 0274" - "ADR 0274"
- "ADR 0273" - "ADR 0273"
@@ -308,27 +306,21 @@ operator's word — and, for the operator's own home, only at the terminal or on
## 9. The account the agent runs as ## 9. The account the agent runs as
*Added 2026-10-08 by ADR 0266; changed 2026-10-17 by ADR 0411 and ADR 0412.* *Added 2026-10-08 by ADR 0266.*
Everywhere above, "the operator's home" and "the operator account" mean **the account the agent runs as**: Everywhere above, "the operator's home" and "the operator account" mean **the account the agent runs as**.
the machine account of the identity the module runs for (ADR 0412). claude-code declares that it can run for That is the operator account, unless the node names an **agent account** of its own. The control node does.
an agent identity, in its assignment or when a session is started: started for `nox` it runs with the agent's
home; started for the operator, it is the default run, with the operator's configuration. On a
machine where no agent is marked as the agent account, its assignment is the default run, as every module's is.
**The fact.** The machine accounts come from the identity seat (ADR 0411); the operator no longer states the **The fact.** The operator states the agent account at the controller's terminal. No verb and no setting
agent account at the controller's terminal. A machine's agent account is the machine account of the agent the writes it. The controller resolves the machine facts `agent-account`, `agent-home` and `agent-root`, and gives
identity seat marks as the agent account there, on the operator's Approve: `nox` on the control node. There this module's the tool runner the account and its home. Where the node names none, the facts name the operator account and
assignment is run for that agent, and the controller refuses one that is not. The controller resolves the `agent-root` is empty, so nothing changes there.
machine facts `agent-account`, `agent-home` and `agent-root` from it, and gives the tool runner the account and
its home. Where no agent is marked (every other machine today), the facts name the operator account and `agent-root`
is empty, so nothing changes there, though a login `nox` exists for the modules run for it.
**What the module declares.** **What the module declares.**
- No login. The mesh makes the agent's machine account (home, own group, no password) from the identity, and the - A `user` named by `agent-account`, with `root` set from `agent-root`. Where the account is the agent's own,
node-engine judges on every look that it cannot become root without a person, by the ways ADR 0266 §12 to §17 list. the mesh creates it (home, own group, no password) and the node-engine judges on every look that it cannot
It detects those ways and no others; it prevents nothing. The `user` resource named by `agent-account`, become root without a person, by the ways ADR 0266 §3 lists. It detects those ways and no others; it
which this module declared until ADR 0411, goes. prevents nothing.
- `~/.claude` under that account's home, owned by it. - `~/.claude` under that account's home, owned by it.
- `claude-agent`, a command that starts the agent as that account through the operator's own sudo, or - `claude-agent`, a command that starts the agent as that account through the operator's own sudo, or
directly when already run as it. directly when already run as it.
@@ -2,7 +2,7 @@
layer: to-be layer: to-be
status: in-progress status: in-progress
code: [mesh-controller, mesh-catalog modules/mesh-delivery] code: [mesh-controller, mesh-catalog modules/mesh-delivery]
updated: 2026-10-17 updated: 2026-10-16
decisions: decisions:
- "ADR 0408" - "ADR 0408"
- "ADR 0407" - "ADR 0407"
@@ -485,8 +485,8 @@ What the build proves before this design is implemented (research 055's directio
## Milestones ## Milestones
Confirmed by the operator on 2026-10-17 (playbook 04). Each is one release of playbook 07; the next starts Confirmed by the operator on 2026-10-17 (playbook 04). Each is one feature step of playbook 07; the next starts
when the previous one's delivery is done. when the previous one's delivery is done. Milestones 5 and 6 are split into steps when they come up.
| # | Outcome | Acceptance tests | Repositories | Issues | | # | Outcome | Acceptance tests | Repositories | Issues |
|---|---|---|---|---| |---|---|---|---|---|
@@ -1,8 +1,8 @@
--- ---
layer: to-be layer: to-be
status: in-progress status: designed
code: [mesh-controller, mesh-host, mesh-sdk, mesh-catalog] code: [mesh-controller, mesh-host, mesh-sdk, mesh-catalog]
updated: 2026-10-17 updated: 2026-10-14
decisions: decisions:
- "ADR 0348" - "ADR 0348"
- "ADR 0349" - "ADR 0349"
@@ -61,10 +61,6 @@ both, by the form of its manifest, and the old forms stay accepted until the las
Inside the release this still holds: the media catalogue and the applications kept in their own Inside the release this still holds: the media catalogue and the applications kept in their own
repositories move after it, so the old forms stay accepted until they have (R1). repositories move after it, so the old forms stay accepted until they have (R1).
**Where it stands, 2026-10-17** (the Milestones section below). The packages' branches were merged into
main of mesh-controller, the node-engine (`mesh-host`) and mesh-catalog on 2026-10-16. T6 moved after them, as
planned above, and is resolved. B3 was left out as well, so B3, R1 and R2 are what is left.
## What exists already, measured ## What exists already, measured
Counted 2026-10-14 at mesh-controller `1601cb1`, the node-engine (`mesh-host`) `1a26778`, mesh-sdk `a2d2af0`, mesh-catalog Counted 2026-10-14 at mesh-controller `1601cb1`, the node-engine (`mesh-host`) `1a26778`, mesh-sdk `a2d2af0`, mesh-catalog
@@ -337,17 +333,6 @@ removed code.
amended, the 67 documents that still say "setting" in running prose are reworded so the glossary can amended, the 67 documents that still say "setting" in running prose are reworded so the glossary can
retire the word (F4 left it off the *Not:* line for them), design 49 lists the Config domain, ADR 0348's and 0349's built status is said, and this document's status becomes `implemented`. retire the word (F4 left it off the *Not:* line for them), design 49 lists the Config domain, ADR 0348's and 0349's built status is said, and this document's status becomes `implemented`.
## Milestones
Written back on 2026-10-17 (playbook 07, "The design's state is written back"): the packages above were built
as one delivery, not milestone by milestone, so the milestones are what was delivered and what is left.
| # | Outcome | Packages | Issues | State |
|---|---|---|---|---|
| 1 | Config, provisioning, secrets, bindings, collections and runtime asks run the new way; the old forms are still accepted | every package but T6, B3, R1, R2 | 647, 650 to 679 but 659, 681, 684, 685 | merged into main and delivered on 2026-10-16; issues resolved |
| 2 | The media catalogue and the applications carry their file text as templates | T6 | 659 | resolved; delivered on 2026-10-16 |
| 3 | The other providers answer their consumers; the old forms are refused; the record says what runs | B3, R1, R2 | 680, 682, 683 | open: built after design 47's milestones are delivered and before design 58 starts (the operator, 2026-10-17; one design is built at a time, playbook 07) |
## What is deliberately not here ## What is deliberately not here
- **Secrets in a module's own code** wait on issue 569 (a module's tools run as the tool runner's - **Secrets in a module's own code** wait on issue 569 (a module's tools run as the tool runner's
@@ -2,7 +2,7 @@
layer: to-be layer: to-be
status: designed status: designed
code: [mesh-catalog, mesh-media-catalog, mesh-sdk, mesh-tools, mesh-controller, mesh-host, mesh-lab] code: [mesh-catalog, mesh-media-catalog, mesh-sdk, mesh-tools, mesh-controller, mesh-host, mesh-lab]
updated: 2026-10-17 updated: 2026-10-16
decisions: decisions:
- "ADR 0383" - "ADR 0383"
- "ADR 0384" - "ADR 0384"
@@ -12,10 +12,6 @@ decisions:
- "ADR 0388" - "ADR 0388"
- "ADR 0389" - "ADR 0389"
- "ADR 0390" - "ADR 0390"
- "ADR 0411"
- "ADR 0412"
- "ADR 0413"
- "ADR 0414"
--- ---
# 58 — Agent work as the mesh's own # 58 — Agent work as the mesh's own
@@ -25,10 +21,8 @@ bus by a thread seat; a thread handler on an always-on node runs the agent harne
of its own, with the thread's own branch on the forge as its workspace; the agent works through a workspace toolkit 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 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 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. An identity is also its logins: the mesh makes each against the role, tool by tool, by the effect every tool carries.**
person's and agent's machine account on every machine it is present on, gives each its own bus password, and runs a (ADR 0383 to ADR 0390, from research 053.)
module for an identity only where the module is set to; everything else runs as the operator.**
(ADR 0383 to ADR 0390, from research 053; ADR 0411 to ADR 0414, from research 057.)
**Acceptance.** The operator starts a thread from the terminal client, an agent on an always-on node works it in a **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 sandbox, asks a question the operator answers in place, and the thread moves to another node and carries on with
@@ -112,95 +106,6 @@ agent carries a token the identity seat signed for that agent, and the tool runn
refusal names the missing grant; every call is recorded with its identity and thread. A session on an operator's refusal names the missing grant; every call is recorded with its identity and thread. A session on an operator's
machine calls as the operator's person identity or as an agent, never through the machine's tool-runner login. machine calls as the operator's person identity or as an agent, never through the machine's tool-runner login.
### 5.1 An identity on the machines (ADR 0411, ADR 0414)
A person or agent identity also holds a **login name**, the **machines** it is present on and, for a person, the
login name kept on a machine where it differs. The seat starts with two: the operator's person identity and `nox`, the one
agent; every piece of agent work is a thread of `nox` (§2), and a second agent identity is made only when a rule needs
another role. The agent is `nox` everywhere: its login, its forge account (the forge account `nox-agent` renamed,
keys and team kept), Keycloak, mail and the bus. A login or service account the mesh makes for the operator from now on carries the operator's identity name;
the operator's existing logins and forge account are kept as they are. On a machine where agents ran as the
operator's login, that login stays the operator's and `nox` gets a login of its own; nox's bus account is `agent.nox`. `nox` is also the
operator's terminal command (`mesh-cli`), and prose says which is meant.
An identity's login on one machine is its **machine account**. The controller composes the machine account of every
identity present on a machine into that machine's declaration, and the node-engine applies it with its `user`
resource. A login that exists is adopted under its name, uid and home; the mesh never renames or deletes one. An
agent's machine account has its own home and group, no password, no root group and no sudo rule, and the node-engine
judges on every look that it cannot become root without a person, by the ways ADR 0266 §12 to §17 list. No module declares a login for a
person or an agent: claude-code's `agent-account` resource goes, and the catalogue's check refuses a manifest that
declares one. The node record's operator account and agent account are read from the identities, no longer stated
at the controller's terminal or in the mesh-config snapshot; the machine facts `account`, `agent-account` and their
homes keep their names. A machine names an agent account only where the identity seat marks an agent as its agent
account: the control node, marked `nox`, where the guard and the licence stay as they are. The other machines are
marked with none, so the guard stays off there and the operator's sessions keep the mesh's tools, while the login `nox` serves the
modules run for `nox`. Where a person or an agent is present, its kept logins and the agent account mark change only
through a verb of the identity seat that only the operator calls, on an Approve; an assignment never sets the mark,
because an agent may call the verb that changes an assignment (ADR 0266 §4). The verb, and the controller at composition,
refuse an agent whose login on a machine would be `root`, a person's login there or a well-known service account
(ADR 0266 §5). The self-check judges every agent login,
marked or not. The seed: the operator on every machine, with the operator's existing login kept where it differs from the
identity's name; `nox` on every machine, the agent account on the control node.
```
identity seat controller each machine (node-engine)
───────────── ────────── ──────────────────────────
person operator, machines, composes, per machine: the operator's login (adopted)
the login kept on each the machine accounts present nox, root: never (adopted on the control node,
agent nox, its machines the facts account, agent-account made elsewhere)
each identity's bus password ──► its secret, owned by its login, 0600
```
### 5.2 Run for an identity (ADR 0412)
Every module runs as the operator account of its machine, as today, with no setting; running for the operator is that
default run. A manifest may declare that its module can **run for** an agent identity, at assignment, at start or both: serve one identity with that identity's machine account, home and bus password.
An assignment of such a module may name the identity it runs for, and the module may let the one who starts it
choose: claude-code started for the operator is the default run with the operator's configuration, and started for `nox`
it runs for `nox` with the agent's home. A start
goes down only: a module is started for an identity by a process already running as that identity's login, or by the
operator's own login for an agent; an agent never starts one for a person, and the module refuses it, naming who asked
and for whom. A module may be assigned to one machine once per identity it runs for there, each its own process under
its own login, and the controller keys an assignment by machine, module and identity. The first two to declare it are
claude-code and ssh-client. On a machine the identity seat marks with an agent account, claude-code's assignment is
run for that agent (`nox` on the control node), so its licence and home scope stay in the agent's home, and the controller refuses
one there that is not. ssh-client keeps its default assignment for
the operator wherever `nox` is present and gains a second one run for `nox`, which makes the agent's forge key in the
agent's home and closes issue 989; gitea's provider registers that key on the forge account `nox`. A module run for an identity calls as that
identity and gets no module identity of its own (this narrows ADR 0386 §4, which holds for every other assignment). A module with an account of its
own (`runs-as`, ADR 0259 §8) cannot declare it, and a run for an identity with no machine account on that machine is
refused at composition and at start. ADR 0266's guard
stands: on the control node, a session started for the operator gets the operator's configuration and its tool calls are still
refused.
### 5.3 A bus password per identity (ADR 0413)
Each person and agent identity has a bus account of its own (`person.<name>` for the operator, `agent.nox`), whose memberships are made
from its role, and its password is a secret owned by its machine account at mode 0600 on each machine it is present
on, which no other account but root can read. A person's password is placed only on a machine that names an agent
account of its own judged unable to become root, by a verdict at most fifteen minutes old; on the other machines, where agents can still run as the operator,
it is not placed, and the controller says so. A module run for an identity reaches the bus with that password, can do
only what the identity's role allows, and every call is recorded as that identity's. Nox's role never holds an approval
grant, a trusted party's grant or a dangerous tool named in it (ADR 0387 §4 does not apply to it), and the identity seat
refuses one; each dangerous act of nox's is asked one by one. The break-glass agent (§6) keeps its own rules and gets
no login and no bus password from this. This changes ADR 0259 §8, which kept every bus credential out of an agent's account; the rest of §8 stands.
The password lives in today's list of bus accounts and is replaced by a signed login for the same role at the bus
switch (§7, §8).
### 5.4 Traced to GENESIS
Checked on 2026-10-17 (playbook 02, step 1); no conflict was found.
- [Mission](../../00-META/mission.md): agents "hold identity", and there is one kind of participant, some of them
human; persons and agents are held alike, with the same fields, and differ in what their role allows, which the
mission calls modality. "The mesh brokers credentials": the bus password per identity and the service accounts made
from it. "The mesh owns the truth": logins are derived from identities, no longer stated per node. "Dogfood": logins
go through the node-engine's existing resource.
- [Context](../../00-META/context.md): "Identity is mesh-internal", with no directory and no SSO, so the identity seat
holds it and Keycloak only receives accounts; "Nodes are personal" and the mesh is a guest, so existing logins are
adopted, never renamed; human agents are few, often one, so the seat holds two identities and one agent.
- [Effect](../../00-META/effect.md): a human agent's environment is part of the mesh, its login included.
## 6. Effects, asks and break-glass (ADR 0387) ## 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: Every seat verb and module tool is one permission and carries one **effect**, set in its module's manifest:
@@ -251,36 +156,12 @@ carry them:
`research/053-signed-bus-logins`, loses nothing and leaves every identity on the new bus. `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. 5. The acceptance sentence at the top, end to end.
From research 057 (ADR 0411 to ADR 0414):
6. The identity seat lists the operator and `nox` with their machines, kept logins and the control node's agent account mark; a
create or a presence change by any caller but the operator's Approve is refused.
7. On every other machine a login `nox` exists; on the control node the login `nox` is adopted with its uid and home unchanged;
on every machine the node-engine judges `nox` unable to become root; a login `nox` given a sudo rule in a test is left
unaltered and the self-check raises `agent-can-become-root` for it, on a machine that marks no agent account too;
the operator's existing logins are
unchanged.
8. No manifest in the catalogue declares a login for a person or an agent; the catalogue's check refuses one.
9. For every machine, the declaration before and after differs, for the modules not set to run for an identity, only
in the machine accounts' `user` resources (now the controller's, and `nox` added on the other machines); claude-code on the control node, run for `nox`, places the same
files in the same home; on the other machines the guard stays off and the operator's sessions keep the mesh's tools.
10. A session started for `nox` on the control node gets the agent's home configuration and one started for the operator the
operator's, with no tool call through; a session as `nox` that starts claude-code for the operator is refused; with
ssh-client run for `nox`, nox's key is on the forge account `nox` and the session pushes a branch over SSH as `nox`.
11. Nox's bus password is owned by `nox` at mode 0600 on every machine nox is present on; a call with it outside
nox's role is refused; a grant test finds no approval grant, trusted party's grant or dangerous tool in nox's
role; the operator's password is placed only on a machine with an agent account judged root-free within fifteen
minutes, and the controller says so for every other.
12. The forge account `nox-agent` is renamed `nox`, keeps every machine's key, stays in the agents' write team, and
every `forge-account` entry names `nox`.
## 11. Milestones ## 11. Milestones
Each milestone is one feature step (playbook 07), usable as soon as its delivery plan is done, and starts when Each milestone is one feature step (playbook 07), usable as soon as its delivery plan is done, and starts when
the previous one's is. What it replaces is in the as-is documents the previous one's is. What it replaces is in the as-is documents
[15 — The agent and its licences](../00-as-is/15-the-agent-and-its-licences.md) and [15 — The agent and its licences](../00-as-is/15-the-agent-and-its-licences.md) and
[16 — Logins on the bus](../00-as-is/16-logins-on-the-bus.md), and for milestones 11 to 14 [16 — Logins on the bus](../00-as-is/16-logins-on-the-bus.md). The issues are opened once the operator confirms
[17 — Accounts on a machine](../00-as-is/17-accounts-on-a-machine.md). The issues are opened once the operator confirms
the milestones, and their numbers written here. the milestones, and their numbers written here.
**Confirmed by the operator on 2026-10-16.** One issue per milestone: 1 is issue 878, 2 is 879, 3 is 880, 4 is **Confirmed by the operator on 2026-10-16.** One issue per milestone: 1 is issue 878, 2 is 879, 3 is 880, 4 is
@@ -305,18 +186,3 @@ and the issue tracker takes only registered owners. The building waits until the
The front ends' later parts, the inference seat and the other agent harnesses are tracked by issue 850; the The front ends' later parts, the inference seat and the other agent harnesses are tracked by issue 850; the
seat-sense "role" in older designs by issue 872. seat-sense "role" in older designs by issue 872.
**Milestones 11 to 14, from research 057, confirmed by the operator on 2026-10-17.** One issue per repository
part, opened at the confirmation, so a fixing agent per repository can start when its step does. Milestone 11
waits for nothing; 12 waits for milestone 6.
| # | Outcome, in one sentence | Proves | Repositories and their parts (issues) | Depends on |
|---|---|---|---|---|
| 11 | The agent's forge account is `nox`, with its keys and its place in the agents' write team kept | test 12 | mesh-catalog gitea: the rename, keys registered on `nox`, every `forge-account` entry (993) | — |
| 12 | The mesh makes the operator's logins and nox's on every machine from the identities, and judges nox unable to become root everywhere | tests 6 to 9 | identity module: logins, machines, kept logins, the agent account mark, the operator's verb (994); the node-engine (`mesh-host`): existing logins adopted, every agent login judged (995); mesh-controller: machine accounts composed, node accounts read from the identities, the terminal lines retired (996); mesh-catalog: claude-code declares no login, the catalogue's check refuses one (997) | 6 |
| 13 | The operator and nox each reach the bus with their own password, held to their role; the operator's only where no agent runs as the operator | test 11 | mesh-controller: the operator's and nox's bus accounts, secrets at 0600, the fifteen-minute verdict (999); identity module: nox's role refuses approvals and dangerous tools (998); until milestone 7 builds the asks, a dangerous act of nox's is refused, not asked | 12 |
| 14 | The operator starts a claude-code session for nox or for themself, and nox pushes over SSH with its own key | test 10 | mesh-controller: run for, assignments keyed by identity, the refusals (1001); mesh-sdk: calls as the identity, a start goes down only (1002); claude-code: started for nox or for the operator (1003); ssh-client and gitea: nox's own key, closing issue 989 (1004) | 11, 13 |
The agent's and the operator's accounts in Keycloak and mail (ADR 0414 §2 and §3) follow milestone 13 and are
tracked by issue 1000, which needs a design section first. A second agent identity is made only when a rule
needs another role (ADR 0414 §1); no milestone makes one.