Skip to main content

Layers

A review lens for separation of concerns, and the anatomy every pluggable seam in Mesh is expected to grow. This document is reasoning, like philosophy.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

Identity and perspective are not a layer. They are the vertical that cuts through all five: reach is credentialed per agent and install, context is perspective-scoped in SQL, governance keys admission on (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.

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:
  1. 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.
  2. 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 | public disposition 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 is manifest.toml because 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.
  3. 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.
The test is blunt, and it is the test to apply in review before a seam’s second implementation merges: adding the next implementation may touch its own package and one catalog line. Every additional file it must touch is a leak — name it, and either fix the seam or record the acceptance where the seam’s doc lives. “Two adapters do not earn a registry” remains true for the catalog (one switch, written once, is a fine catalog at N=2); it was never a license for five switches in five files.

Where each seam stands

Status detail belongs to roadmap/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.

What this model does not claim

It does not claim packages map one-to-one onto layers, that every seam needs a TOML file, or that the layers replace the identity, perspective, or governance principles — those remain prior. It claims only that “which layer owns this concern?” and “does this seam have its three parts?” are questions cheap enough to ask in every review, and that the recorded cost of not asking them is already visible in the seams above.