Layers
A review lens for separation of concerns, and the anatomy every pluggable seam in Mesh is expected to grow. This document is reasoning, likephilosophy.md. The normative
rules it implies live in exactly one place —
roadmap.md §Design guardrails (repository) — and the
contract for any specific seam lives in that seam’s own doc. What this document
adds is the map: which layer a concern belongs to, what question tells you it
has leaked into the wrong one, and what a seam must have before its second
implementation lands.
Why a layer model at all
Mesh’s failure-mode analysis is identity-shaped (philosophy.md),
but its code-review failures are layer-shaped: a vendor client synthesizing
prompt text, a payload decoder making an engagement decision, a settings page
speaking one backend’s vocabulary. None of those are caught by the identity
principles, because each one is locally reasonable. They are caught by asking a
different question: which layer does this concern belong to, and is this code
in that layer?
The layers are a review construct, not a package layout. A package may
implement contracts from more than one layer (a connector carries both a
transport contract and a presentation contract); the boundary lives in the
contracts, not in the directory tree.
The five layers
(agent, conversation),
surface attributes every delivery, explanation scopes every read. A layer that
handles an unattributed value has already failed before the layer question is
asked. See perspective.md.
1 · Reach — what an agent can touch
Tools, providers, execution and browser backends, model connectors, transport connectors, and the credentials that scope all of them. The layer’s rule is the cloud-first principle: nothing about reach derives from the host; every capability crosses a declared, granted, auditable boundary.- Packages:
internal/tool,internal/execution,internal/model,internal/connector(the transport half),internal/agent,internal/secrets,internal/settings. - Contracts:
runtime/tools.md,runtime/sandboxed-execution.md,connectors/slack.md,connectors/model.md. - Leak test: does anything above an adapter know a vendor’s name, or does any adapter do work another layer owns? A model connector that writes prompt text, a backend name in a settings type, a tool that infers its own class — all reach leaks.
2 · Context — what an agent knows this turn
The bounded projection of the conversation graph, memory, persona, and identity into one model request. The layer’s rule is the anti-blob principle: context is structured and attributed until the last possible moment, and the assembled prompt is a view of durable state, never the state itself.- Packages:
internal/prompt,internal/graph(read side),internal/memory,internal/state. - Contracts:
runtime/context-resolution.md,runtime/memory-retrieval.md,versioned-state.md. - Leak test: can a context source be added, or a rendering changed, without
touching data access or the loop? Rendering that lives outside
internal/prompt, context that arrives pre-flattened, or a surface name in prompt text are context leaks.
3 · Judgment and governance — what happens, and what is allowed to
One layer with two owners, and the split is Mesh’s central move (philosophy.md §runtime-governance):
the model owns judgment (interpret, choose, propose); the runtime owns
invariants (addressing, admission, budgets, schema validation, effect policy,
the commit boundary). Everything deterministic here must be explainable,
including the decision to do nothing.
- Packages:
internal/addressing,internal/turn,internal/run,internal/claim,internal/app(governor, ingress, runtime orchestration). - Contracts:
runtime/addressing.md,runtime/turn-pipeline.md,runtime/agent-harness.md,runtime/interrupt-model.md,runtime/work-graph.md. - Leak test: is any decision made where its inputs don’t live? Engagement policy in a payload decoder, a bound defined independently in two packages, or a runtime decision smuggled through reply text (“empty string means declined”) are governance leaks.
4 · Surface — how output reaches the world
Canonical output, per-connector rendering, delivery, receipts, and lifecycle signals (acks, reactions). The layer’s rule is render-at-send: the graph stores canonical Markdown; a wire format exists only inside the connector that is that format (runtime/turn-pipeline.md
§Render at send).
- Packages:
internal/render,internal/connector(the presentation half),internal/turn(the send boundary). - Leak test: does any layer above a connector know how a surface formats, addresses, or acknowledges? Surface names in persisted vocabularies, wire formats in stored bodies, and inbound wire markup reaching the model unconverted are surface leaks — the last one is the inbound mirror of the compounding bug and is just as real as the outbound case.
5 · Explanation — how an operator reconstructs the rest
Capture, inspection, measurement, and the improvement loop. The layer’s rule is capture-before-behavior with its stated asymmetry: capture may never lag behavior; display may lag capture — but a capture no read path ever serves is only half-delivered, and a behavior nothing scores cannot be improved.- Packages:
internal/admin,internal/telemetry,internal/errtrack,internal/run(the read side),web/. - Contracts:
runtime/observability.md,telemetry.md,error-tracking.md. - Leak test: can the operator reconstruct, from durable data through the read
API, what the agent observed, why it acted or stayed silent, what authority
it used, and what it cost? Anything answerable only via
psql, a log line, or a trace backend is an explanation gap. So is any layer that produces behavior capture cannot see — a synthesized instruction, an unrecorded shed.
The seam anatomy
runtime/tools.md §The tool package format
settled more than tool packaging. It is the template for what any pluggable
seam in Mesh must eventually have, because every part of it answers a failure
the other seams have already exhibited. A seam has three parts:
- Contract — a small Go interface plus value types, vendor-free, capability-declared. Code above the seam branches on a declared capability, never on an implementation’s name. Every seam in Mesh has this part, and it is the part Mesh is consistently good at.
-
Descriptor — the implementation’s declarative self-description, readable
without executing its code: identity, capabilities, and its config
surface — each field with the closed
secret | config | publicdisposition vocabulary from the tool manifest. The descriptor is what the settings UI, the enablement flow, and the audit view render from. For tools the descriptor ismanifest.tomlbecause operators and (eventually) third parties read it; for compiled-in adapters a Go value serves — the format is incidental, the property is not: when the descriptor is missing, the layer above the adapter hand-codes what the descriptor should have declared, in the vendor’s vocabulary. That is precisely how a backend’s name ends up in settings types, admin routes, and page code. -
Catalog — exactly one registration point mapping a stable name to an
implementation. Generated (the tool handler catalog) or hand-maintained in
one place (the error-tracker
ProviderLookup); either way, unknown names fail loud and the N+1th implementation edits its own package plus one catalog entry, nothing else. A seam whose selection logic is written more than once does not have a catalog, it has switches — and each switch is a place the next implementation gets forgotten.
Where each seam stands
Status detail belongs toroadmap/current-state.md (repository);
this table records only which anatomy parts each seam has. Most rows are a
historical snapshot at migration 0026; the Tools row is updated on 2026-09-18.
See the tool strategy for the current
baseline and planned common catalog.
Model providers were this table’s control experiment — the only seam with two
live implementations, whose five-switch spread measured the cost of a missing
descriptor and catalog — and execution backends were one implementation old and
already exhibiting the same spread. Both were retrofitted to the full anatomy
(the execution retrofit also un-flattened the stored settings document,
0026,
before a second backend could collide with it). The remaining 🟨 and ⬜ cells
above are the ones to close before — not after — their next implementation lands.
Native tool packages and remote MCP bindings now run in production. The next
step is the shared integration layer, with
Linear retirement gated on MCP parity. Browser interaction has a separate
multi-provider implementation tracked by that strategy’s baseline.
Conversation connectors took their second implementation (Telegram) without a
descriptor and paid the predicted spread — the per-file bill is recorded in
connectors/telegram.md, and closing it comes before
a third connector. Context sources close before the next prompt ingredient.