Skip to content
GitHub
Decisions

ADR-070: Inter-context mechanism selection — simplest-fit ladder

Status: Accepted (2026-04-30) — partially supersedes ADR-065 D4 default-mechanism framing; refines ADR-063 §2 framing

Context

ADR-065 established the inter-context contract surface: producer-owned typed event payloads (D2), callee-owned OHS Protocols (D3), notification-flow handling (D4), and bridge tools (D5). ADR-063 §2 prescribed the callee-owned OHS Protocol + impl in callee context + bridge tool in apps/* shape as “the pattern” for call flow. Together, the two ADRs framed inter-context mechanism selection as a binary: notification → events; call → OHS Protocol.

The wave-5 mints during SPEC-252’s original refinement (EnshrinementGate, WorldModelPublisher, PendingCandidateReader, WorldModelHealthReader all minted as OHS Protocols at spectral.core.tools.protocols) demonstrated the binary framing tilts toward over-engineering. Each of the four had a single Python in-process consumer (an apps/api operator route module) — a use case handler in <context>.application.* consumed via validator rule 7 would have done the job. The OHS Protocol layer added DI indirection, contract-surface curation, and (under ADR-065’s substrate framing once relocated) bilateral contract-test scaffolding, all in service of conditions that did not hold.

The principle that needed explicit doctrine: simplest solution wins; escalate only when the simpler choice fails an explicit eval. ADR-070 makes that principle the inter-context selection rule, with concrete tiers and concrete eval criteria.

Decision

D1. Inter-context mechanism selection follows a simplest-fit ladder

Each tier is the default for its problem shape; escalation requires an explicit failure of the tier below.

TierMechanismWhere it livesDefault for
1Use case handler — direct callable<callee>.application.*Single-Python-consumer call flow (e.g., apps/api route)
2Callee-owned OHS Protocol + implProtocol at <callee>.contracts.protocols.*; impl at <callee>.application.*Multi-framework-consumer call flow; LLM-tool wrapping; cross-process; polymorphism
3Event substrate — producer-typed payload + consumer ACLPayload at <producer>.contracts.events.*; substrate from spectral.core.eventsNotification flow (producer doesn’t await)
4Bridge tool — composes Tier 2 Protocol into a framework deliverableapps/workers/tools/ or apps/mcp-<bridge>/Agent-to-agent delegation
5Direct import between contexts(forbidden — per ADR-065 D7)ADR-mint per-exception only

Tier 1 — Use case handler in <callee>.application.* (call-flow default).

The framework deliverable imports the use case directly per ADR-065 D7 + validator rule 7. Direct callable; no Protocol indirection. The application layer IS the context’s published surface in Clean Architecture; use case handlers ARE the context’s API surface for inter-context call flow. The default for apps/api request-scope routes invoking context use cases.

Tier 2 — Callee-owned OHS Protocol in <callee>.contracts.protocols.* (per ADR-065 D3).

Required when one or more is true:

  • Multiple framework consumers need the surface (e.g., apps/workers/tools/ + apps/api + apps/test-agents).
  • LLM tool wrapping with observability + error-mapping per ADR-060 D5 (the apps/workers/tools/ pattern).
  • Cross-process / cross-language boundary (e.g., apps/mcp-<bridge>/).
  • Genuine polymorphism — multiple impls need composition-time swap.

Tier 3 — Event substrate (notification flow) (per ADR-065 D2 + D4).

Producer broadcasts a typed payload; consumer parses with its own local ACL model; consumer projects into local state. Used when flow shape is notification (producer doesn’t await result).

Tier 4 — Bridge tool in apps/* (per ADR-065 D5).

When an agent in one context invokes a capability in another context through the framework layer (agent-to-agent delegation; e.g., Ops Agent → World Agent). The bridge composes a Tier 2 Protocol — Tier 4 implies Tier 2.

Tier 5 — Direct import between contexts (forbidden per ADR-065 D7).

ADR-mint per-exception is the only gate.

D2. Selection rule per inter-context need

Stage 1. Flow shape — notification or call?

  • Notification (producer broadcasts; consumers don’t await) → Tier 3.
  • Call (caller awaits result) → Stage 2.

Stage 2. Apply Tier 1’s two gates:

  • (a) Functional gate — Can a use case in <callee>.application.* satisfy the need?
  • (b) Shape gate — Is direct callable + import the idiomatic shape for the consumer’s behavior characteristics?

Both pass → Tier 1. Done.

Stage 3. If a gate fails, identify which Tier 2 condition triggers escalation; document on the consuming epic. → Tier 2.

Stage 4. If the call is one context’s agent invoking another context’s capability through the framework layer → Tier 4 (which composes a Tier 2 Protocol; Tier 2 is prerequisite).

D3. Eval-criteria precision

  • “Multiple framework consumers” considers near-term realistic consumers, not strict alpha-state count. A Protocol with obvious multi-consumer trajectory (apps/workers/tools shared across all three agents; multiple apps/api routes consuming the same surface) lands at Tier 2 now. Don’t down-tier on “exactly one consumer today” — design for system evolution, not alpha-state minima.
  • Test decoupling alone does not justify Tier 2. Tier 1 use case handlers are mockable via DI of a callable or import-path patching. Tier 2 is reserved for the four conditions in D1.

D4. Substrate Protocols are out of scope

OHS Protocols admitted under ADR-065 D1 at spectral.core.tools.protocols.* (substrate-level Protocols whose killer-test admission passes — e.g., OutboxReader, OutboxReplayer if their substrate-admission eval passes) are substrate-level, not inter-context-level. ADR-070 governs inter-context mechanism selection (between contexts, or context ↔ framework deliverable). Substrate Protocols follow ADR-065 D1’s killer-test admission discipline; the simplest-fit ladder doesn’t apply.

Alternatives considered

  • Keep ADR-065 D4’s “defaults to notification flow” framing. Rejected — the wave-5 evidence shows it produces over-engineering for single-consumer call-flow cases. The default-to-notification claim makes sense when consumers are unknown and notification is the right flow shape; for known single-consumer call flow, it’s wrong.
  • Per-PR judgment without explicit ladder. Rejected — relies on individual judgment about when OHS Protocols are warranted; produces inconsistent decisions and re-litigation per refinement. The ladder pins the criteria so the judgment doesn’t drift.
  • Single doctrine statement: “use case handlers always; escalate on demand.” Rejected — leaves notification flow under-specified, conflates flow-shape selection with mechanism selection, and removes the structural relationship between Tiers (Tier 4 implies Tier 2; Tier 3 is selected by flow shape, not by escalation). The ladder makes those structural relationships explicit.

Consequences

  • ADR-065 D4’s “defaults to notification flow” framing is partially superseded. The notification-flow mechanism description (typed payload, ACL, projection) stands. The “defaults to” claim is replaced with the simplest-fit ladder. Per the doctrine in docs/decisions/overview.md, the superseded segment in D4 is replaced with a one-line pointer to this ADR.
  • ADR-063 §2’s “the pattern per ADR-065 D3 + D5” framing is refined by this ADR’s ladder. The mechanism description (callee-owned Protocol + impl + bridge tool) stands as Tier 2. The implicit claim that this IS the call-flow pattern is refined: this is the Tier 2 escalation; Tier 1 is the default. ADR-063 §2 carries a pointer to this ADR alongside its existing ADR-065 supersession blockquote.
  • SPEC-252 wave-5 OHS Protocols collapse to Tier 1 use case handlers. Affected surfaces: EnshrinementGate, WorldModelPublisher, PendingCandidateReader, WorldModelHealthReader. All four ship as use case handlers in worlds.application.*; SPEC-238 T2.3 reframe ships this shape (no worlds.contracts.protocols.* mints required for the four wave-5 surfaces).
  • SPEC-252 body simplifies. The DI seam composition-factory framing reframes around use-case-handler imports rather than Protocol DI. Import-boundary rules: apps/api may import worlds.application.* per validator rule 7; no worlds.contracts.protocols.* import needed for the four wave-5 surfaces.
  • Existing Tier 2 Protocols stay. WorldAgentRunner, EvalSetProvider, EmbeddingProvider — all multi-consumer or apps/workers/tools wrapping; Tier 2 justified. (T3MemoryReader was on this list at mint time but retired by ADR-064 D3 broadened 2026-04-30 — T3 memory reads are notification-shaped, served by event-driven worlds-local projection rather than a Reader Protocol.)
  • Bridge tools (Tier 4) keep their Protocol prerequisites. Ops Agent / Spectral Agent / World Agent agent-tool surfaces continue using OHS Protocols at <context>.contracts.protocols.* per ADR-065 D5; no change to apps/workers/tools wiring.
  • Codex extension lands alongside this ADR. A new section in system-design/foundations/contract-surfaces.mdx documents the ladder with one worked example per Tier.
  • Broader review queued. Apply the ladder retroactively to all currently-planned OHS Protocols in the M10 re-refinement queue. Initial scan suggests SPEC-252’s wave-5 four are the only over-engineered set; broader review confirms or surfaces additional Tier-1 collapse candidates.

References

  • ADR-001 — three-context topology
  • ADR-031 — single-library + app leaves; framework-layer composition pattern
  • ADR-044 — event substrate (Tier 3 mechanism)
  • ADR-060 — agent tool invocation (Tier 4 worked example; LLM-tool wrapping criterion)
  • ADR-063 — inter-context access pattern (no SQL grants; §2 framing refined here)
  • ADR-065 — inter-context contract surface (D2 → Tier 3; D3 → Tier 2; D5 → Tier 4; D7 → Tier 5; D4 default-mechanism framing partially superseded here)
  • ADR-066 — bilateral contract tests (Tier 3 drift detection)
  • feedback_design_for_system_evolution_not_alpha_state — design for evolution, not alpha-state constraints (D3 eval-criteria precision)
  • project_cross_bc_axis_is_flow_shape — flow shape is the inter-context axis (Stage 1)
  • Codex system-design/foundations/contract-surfaces.mdx — pattern documentation (worked-examples section landing alongside this ADR)