Reconcile: adopt initialization's consolidated HQ as canonical, re-home this session's new work #24

Merged
jschoubben merged 177 commits from reconcile-init-into-main into main 2026-09-05 10:27:11 +00:00
5 changed files with 335 additions and 0 deletions
Showing only changes of commit 3c6c16abdf - Show all commits
@@ -0,0 +1,130 @@
---
topic: what runs on it
status: accepted
date: 2026-08-31
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0004-a-node-and-how-it-joins.md
---
# 26. The mesh has a session of its own, and it is the node session's mechanism
## Context
[ADR 0004](0004-a-node-and-how-it-joins.md) gives every node a session: one per node, permanent,
remembering across callers, its system prompt the node's engram, reachable over the broker like
everything else. **Any node can message any node**, and that is called the one part of the system
that is genuinely a mesh — symmetric, with no centre.
**There is no way to address the mesh itself.** A question that spans machines — *what is running
across all of this*, *which nodes are behind*, *why is it built this way* — has to be put to some
node, which then asks the others. That works, and it makes a mesh-wide question **nobody's
question**: every node answers it as a foreigner, from a position where the whole is not in view.
**Three things independently arrived at the same missing piece.**
[ADR 0025](0025-the-design-record-is-read-not-copied.md), taken hours before this one, commits to
an agent that reads the design repository directly and answers into search. That agent has to
exist, run somewhere, and be askable — and nothing in the record says what it is or where it
lives.
[`14-model-access.md`](../03-DESIGN/01-to-be/14-model-access.md) records, as a gap deliberately
not half-built: *this worker uses that licence is a binding to an agent, not to a node* — and the
provisions model has no consumer identity other than a node. A session that must be assigned a
licence is exactly that consumer, and node sessions are already one.
**And ADR 0004 never said how a session is set up.** It describes behaviour and stops: nothing
states how a session starts, where its context lives, how the engram reaches it, or how a message
off the broker becomes a prompt. There is no design document for it. That gap was invisible until
something had to be built *like* a node session, because describing a second instance of a
mechanism requires the mechanism to have been described once.
## Considered Options
1. **No mesh session; keep relaying through a node.** Costs nothing and works today. **Rejected.**
It leaves mesh-wide questions belonging to nobody, and it does not survive contact with
ADR 0025 — that agent still needs a home, so the thing gets built anyway, unnamed, as an
attachment to whichever node happened to host it.
2. **A new kind of agent, built separately.** Purpose-built for the whole mesh. **Rejected.** It
would hold a session, a memory, a licence and broker plumbing — every one of which the node
session already has. Two implementations of one mechanism drift, and the vocabulary collision
that [ADR 0001](0001-mesh-brokers-nodes-host-agents-think.md) exists to undo began exactly this
way: two things that were nearly the same, built twice, until neither word meant one thing.
3. **The same mechanism, started in a different context.** **Adopted.**
## Decision
**The mesh has one session, addressed as the mesh, and it is a node session in every respect but
three.**
| | |
|---|---|
| **the context it starts in** | the mesh's, not a machine's — this is the whole of what makes it different |
| **its engram** | the mesh's system prompt, as a node's engram is that node's |
| **its licence binding** | assigned in its own right, not inherited from the machine it runs on |
Everything else is unchanged and deliberately so: it is permanent, it remembers, it is reachable
over the broker, it holds its own tools, and switched off it still answers *I am switched off*
rather than falling silent.
**It runs on the node that holds the control plane** — not for convenience, but because that node
is already the one place excepted from *compromise of a node is compromise of that node*
(ADR 0004). An agent able to reach everything, placed anywhere else, creates a **second** such
place. Putting it where the authority already sits concentrates nothing new.
**It is an addition to per-node messaging and never a replacement.** Every node remains directly
addressable. This is not a preference: ADR 0001 holds that losing the control plane costs *change,
not operation*, and a mesh whose only conversational surface lives on that node would lose the
ability to ask anything while every machine kept running perfectly. **The front door may not be
the single point.**
**It is not an employee** ([ADR 0003](0003-agents-are-persistent-employees.md)). Nobody hires it,
it holds no task queue, it is never drained or reassigned. What it does with work that belongs
somewhere else is **dispatch it** — to node sessions, or to workers — which is what a node session
already does when asked something it does not have.
**It is ADR 0025's reader.** The agent that reads the design repository and answers into search is
this session, not a second one. One agent, one memory, one place to reach; two would both need
that repository and would eventually disagree about what it says.
**"One per node" is about address, not about process count.** ADR 0004's rule — *two and nothing
decides which replies* — forbids ambiguity in who answers when a **node** is addressed. The mesh
session answers when the **mesh** is addressed. The control-plane node therefore hosts two
sessions and no ambiguity, and stating this here is what stops it reading as a contradiction
later.
## Consequences
**The node session's setup must now be designed, and it never was.** This decision is expressed as
*the same as a node session, elsewhere*, which is only meaningful once that mechanism is written
down. The design document covering both is the immediate consequence of this record, not a
follow-up to it.
**A consumer that is not a machine stops being deferrable.** The licence binding above is the gap
`14-model-access.md` names, and it now has two consumers rather than a hypothetical one. Until it
exists, a session's model access can only be expressed as *this module on this machine*, which
cannot say *this node's session uses the personal licence and the mesh's uses the company one* —
the thing the binding is for.
**Symmetry is preserved, and it is worth being precise about why.** ADR 0004's claim is about what
a node can reach, and it is untouched: node-to-node messaging is unchanged, nothing is routed
through the mesh session, and it is a participant rather than a hop. What arrives is a
participant that happens to be the one a person usually addresses.
**Availability degrades to inconvenience rather than to silence** — but only because of the
addition rule above. If that rule is ever relaxed, this consequence inverts, and it inverts
quietly: everything keeps working and nobody can ask about it.
**The surface a person uses is not decided here.** That a board is a good place to talk to it is
likely and is not this record's business; the session is reachable over the broker like everything
else, and what puts a text box in front of it is a separate choice.
## References
- [ADR 0004](0004-a-node-and-how-it-joins.md) — the node session this extends
- [ADR 0025](0025-the-design-record-is-read-not-copied.md) — the reader this session is
- [ADR 0003](0003-agents-are-persistent-employees.md) — the vocabulary this is not
- [`03-DESIGN/01-to-be/14-model-access.md`](../03-DESIGN/01-to-be/14-model-access.md) — *a
consumer that is not a machine*, the gap this makes concrete
+1
View File
@@ -98,6 +98,7 @@ python3 00-META/checks/index.py fail if stale
- **0009** — [Modules and the graph](0009-modules-and-the-graph.md)
- **0010** — [Delivery](0010-delivery.md)
- **0024** — [Model access is a provision, and a licence is a thing with a name](0024-model-access-is-a-provision.md) *(proposed)*
- **0026** — [The mesh has a session of its own, and it is the node session's mechanism](0026-the-mesh-has-a-session-of-its-own.md)
### How it is built
+7
View File
@@ -81,6 +81,13 @@ to a node. What is delivered still lands on a machine; what is **chosen** is cho
the provisions model has no consumer identity other than a node. What exists today is per module
per machine, which is a step toward it and is not it.
*2026-08-31: this gap now has named consumers rather than hypothetical ones.*
[ADR 0026](../../02-DECISIONS/0026-the-mesh-has-a-session-of-its-own.md) puts two sessions on the
control-plane node — the node's own and the mesh's — each bound in its own right. **A per-machine
binding cannot express that at all**, not merely awkwardly: the two sessions share a machine and
must be able to hold different licences. See
[`15-the-agent-session.md`](15-the-agent-session.md).
**Switching is a reaction, not a declaration.** A licence that hits its limit and must be swapped is
a response to something observed. Expressing it as a declaration would make the declaration mean
*whatever is working right now*, which is not a thing anybody declared. It belongs with
+192
View File
@@ -0,0 +1,192 @@
---
layer: to-be
status: designed
code: [mesh-control, mesh-host]
updated: 2026-08-31
decisions:
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
- 02-DECISIONS/0026-the-mesh-has-a-session-of-its-own.md
- 02-DECISIONS/0024-model-access-is-a-provision.md
- 02-DECISIONS/0025-the-design-record-is-read-not-copied.md
---
# The agent session
**One mechanism, started twice.** A node's session and the mesh's session are the same thing
pointed at different context. This document describes the mechanism; where the two differ it says
so, and the differences are few enough to list here:
| | node session | mesh session |
|---|---|---|
| **address** | the node's name | the mesh |
| **context root** | the node's | the mesh's |
| **engram** | that node's | the mesh's |
| **licence** | bound in its own right | bound in its own right |
| **runs on** | that node | the node holding the control plane |
| **how many** | one per node | one |
Everything below applies to both unless it says otherwise.
## What a session is, restated for what is being built
[ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) settled the behaviour: permanent,
remembering across callers, its own tools, reachable over the broker, and — switched off — still
answering *I am switched off* rather than falling silent.
**Nothing said how one is set up**, and that gap is what this document closes. It was invisible
until [ADR 0026](../../02-DECISIONS/0026-the-mesh-has-a-session-of-its-own.md) needed to describe
a second instance, because *the same as that one, elsewhere* means nothing until the first has
been written down.
## The context root is the whole of the difference
**A session is defined by the directory it starts in.** That directory holds the engram, the
session's tools, and whatever standing instruction it works under. Two sessions differing only in
their root are two different agents, and nothing else has to differ to make them so.
This is deliberately a **small** definition. The alternative — a session type, with the mesh
session as a distinct kind — would mean two implementations of one mechanism, and
[ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) records what that
costs: two things nearly the same, built twice, until neither word means one thing.
**The root is delivered as declared state, not carried by the session.** It is files on a machine,
which is precisely what the host applies
([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). A session's context therefore changes the
way anything else changes — the mesh declares it, the host writes it — and there is no second
mechanism for shipping an engram.
**Changing the engram is changing a file.** So it is versioned, reviewable, and rolled back like
any other declared state; and a node whose engram was changed reports having applied it, the same
as it reports anything else.
## Where each one runs
**A node's session runs on that node**, and cannot be moved. Moved, one machine is answering as
another (ADR 0004).
**The mesh's session runs on the node holding the control plane.** The reasoning is in ADR 0026
and is worth carrying here because it is easy to get backwards: this is not *the important agent
goes on the important machine*. It is that the control-plane node is already the one place
excepted from *compromise of a node is compromise of that node*, and an agent able to reach
everything, placed anywhere else, would create a second such place.
## Messages
**A session is reached over the broker**
([ADR 0002](../../02-DECISIONS/0002-nodes-communicate-over-a-broker.md)). There is no second
transport, nothing is dialled at a session, and the mesh session is not a hop: node-to-node
messages continue to travel directly, and nothing is routed through it.
**A message becomes a prompt; a reply travels back the same way.** Whoever asked — a person at a
surface, another session, a worker — is a caller, and the session remembers what each of them
asked, together, over time.
**Being asked something it does not have, a session may ask another.** How it does so is its own
business and follows from its engram rather than from a message format: it may say who wants to
know, or simply ask. A person relaying a question makes the same choice.
**A switched-off session answers.** The queue is still read and the state is the reply, with no
model involved. This is the same rule the host follows about a service that does not exist, and
it is the rule this repository has now paid for three times: **absence must never be
indistinguishable from a failure to answer**
([005](../../04-ISSUES/005-pipeline-test-harness-unbuildable/00-report.md),
[008](../../04-ISSUES/008-the-documented-node-rescue-does-not-exist/00-report.md)).
## Model access is bound to the session, not to the machine
**A session is a consumer in its own right.** This is the gap
[`14-model-access.md`](14-model-access.md) records as *a consumer that is not a machine*, and it
is the one part of this design that cannot be built from what exists: the provisions model has no
consumer identity other than a node, so today a binding can only say *this module on this
machine*.
That is not sufficient here, and the shortfall is concrete rather than theoretical:
- the control-plane node hosts **two** sessions, which must be able to hold **different**
licences — a per-machine binding cannot express it at all;
- *this node's session uses the personal licence, the mesh's uses the company one* is the ordinary
case, not an exotic one.
**What is delivered still lands on a machine. What is chosen is chosen per session.** Delivery
follows the session's context root, which is where its credentials belong — the same rule ADR 0001
states for an agent's config directory, with the root standing in for it.
**Switching a licence remains a reaction, not a declaration** (ADR 0024). A session that exhausts
a licence is observed and its binding changed; the binding is then declared as usual. Nothing here
grows a conditional in the declaration language.
## What the mesh session knows
**It holds the design record by reading it**
([ADR 0025](../../02-DECISIONS/0025-the-design-record-is-read-not-copied.md)), not by holding a
copy. It is that reader; there is not a second agent for it.
**It answers into a symptom search**, so what it knows appears beside ordinary results rather than
only when it is asked. **And when it cannot be reached, the search says so.** A result set that
silently omits this material looks identical to one where nothing matched — the same rule as the
switched-off session above, at a different layer.
**Reading is one-way.** It reads the repository and answers from it; nothing flows back. The
repository is public and the mesh is not, and a return path is how installation-specific detail
arrives into documents that must not carry it.
## What it does with work
**It dispatches; it does not hold a queue.** Asked for something that belongs elsewhere, it asks a
node session or hands the work to a worker. It is not an employee
([ADR 0003](../../02-DECISIONS/0003-agents-are-persistent-employees.md)): nobody hires it, it is
never drained, and it is not reassigned.
The distinction is the one ADR 0001 was written to keep: a session comes with the thing it belongs
to and goes when that thing goes; a worker is hired, holds tasks, and moves. Built from the same
parts, run on entirely different terms.
## The surface is separate
That a person usually reaches the mesh session through a board is likely and is not settled here.
The session is reachable over the broker like everything else; what puts a text box in front of it
is a different design, and the session does not know which surface asked.
## Consequences
**Two sessions on one node, and no ambiguity.** The control-plane node hosts its own node session
and the mesh session. ADR 0004's *one per node* forbids ambiguity about who answers when a **node**
is addressed; these answer to different addresses.
**A new node gets a session by being declared, not by being set up.** The context root is declared
state, so a joining node's session arrives the way its packages and services do.
**The mesh session is a single point of convenience, and must never become one of reach.** Every
node stays directly addressable. If that ever stops being true, the failure is quiet in the worst
way: every machine keeps working and nobody can ask about it.
## How it is checked
**A test names the decision it defends** ([ADR 0017](../../02-DECISIONS/0017-a-test-defends-a-decision.md)),
and these run in the lab on real machines:
| Check | Defends |
|---|---|
| a node is asked something and its session answers | ADR 0004 |
| the mesh is asked something and the mesh session answers, on the control-plane node | ADR 0026 |
| both sessions on that node answer, to their own addresses, without ambiguity | ADR 0026 |
| a session switched off replies saying so, rather than timing out | ADR 0004 |
| a session whose engram was changed reports having applied it, like any declared file | ADR 0005 |
| the two sessions on one node hold different licences, and each uses its own | ADR 0024 |
| a node is messaged directly while the mesh session is stopped, and answers | ADR 0001 |
| a search consulting an unreachable mesh session says it was not consulted | ADR 0025 |
The last two are the ones worth writing first. Both defend properties that are invisible while
everything works, and both describe a mesh that looks entirely healthy at the moment it has
stopped telling the truth.
## Deliberately not decided
**Whether the mesh session's memory is its own or assembled from the node sessions each time.**
It remembers what it has been asked, which is settled. Whether *what the mesh knows* is a thing it
holds or a thing it gathers on demand is a real question with a cost either way, and nothing here
depends on the answer yet.
**How a person's identity reaches a session.** Callers are distinguished, but who a caller *is*,
and whether a session should act differently for different people, is the human-agent question
ADR 0001 leaves open and this does not close.
+5
View File
@@ -20,6 +20,11 @@ document is written and this one's status becomes `implemented`.
| [`08-connectivity.md`](08-connectivity.md) | One context in full — overlay, resolution, exposure, filtering, certificates | [ADR 0007](../../02-DECISIONS/0007-connectivity.md), [0050](../../02-DECISIONS/0007-connectivity.md), [0051](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [0055](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) |
| [`09-the-node-lifecycle.md`](09-the-node-lifecycle.md) | How a machine becomes a node, stays one, and stops being one | [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [0051](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) |
| [`10-delivery.md`](10-delivery.md) | Modules, the three edges, and how a change becomes a running thing | [ADR 0010](../../02-DECISIONS/0010-delivery.md), [0064](../../02-DECISIONS/0009-modules-and-the-graph.md), [0065](../../02-DECISIONS/0009-modules-and-the-graph.md) |
| [`11-a-board.md`](11-a-board.md) | What a person sees of the mesh, and why it is read from what runs | [ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md), [ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) |
| [`12-a-module-repository.md`](12-a-module-repository.md) | A module repository, and what builds it | [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md), [ADR 0010](../../02-DECISIONS/0010-delivery.md), [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) |
| [`13-credentials-and-their-rotation.md`](13-credentials-and-their-rotation.md) | Credentials, and moving them without a consumer holding one the provider does not know about | [ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md), [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) |
| [`14-model-access.md`](14-model-access.md) | Model access as a provision, and what a licence is bound to | [ADR 0024](../../02-DECISIONS/0024-model-access-is-a-provision.md), [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) |
| [`15-the-agent-session.md`](15-the-agent-session.md) | One mechanism started twice — a node's session and the mesh's | [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [ADR 0026](../../02-DECISIONS/0026-the-mesh-has-a-session-of-its-own.md) |
## Not yet written