How a tool call gets from a coding agent to your app and back, what each hop can do wrong, and which of those failures are silent. Written for anyone touching the transport, the daemon, the bridge, or a gate — and for contributors trying to work out where their change lives. Companion pages:harness-rules.md(what a gate must do about all this) andtelemetry-contract.md(the rules for anything that emits).
1. Topology — 5 processes, 4 hops
Each hop has its own failure vocabulary, its own recovery, and its own way of lying. Nothing stated this end to end until this page, which is why gate results have been contradictory.2. Connection establishment
Sequencing hazard: steps 6 and 7 race. The product absorbs it internally — the first live tool call blocks briefly for a session to appear rather than failing — but a gate that boots an app and immediately asks whether a session exists is outside that protection and has been burned by it. Poll for the session; never sample once.
3. The tool graph — what produces what
Three different counts, all measured, and the difference between them matters:
68 → 48 is family folding:
reticle_baseline, reticle_session, reticle_record, reticle_flow and reticle_lease each absorb their members behind an action parameter. So “every tool is callable” is asserted over 48 surfaces, not 68 behaviours — a family member reachable only through an action value the sweep never passes is not covered by it. Worth closing when the per-tool budget lands (see gate-plan.md, Phase 4).
Two structural facts:
reticle_runis a second dispatch path. It routes throughrunTool, so telemetry is intact, but anything decorating results aboverunToolsilently does not apply. The session lease is the instance we know about; the next decorator inherits the same bug.record → save → verify → healis the only model-free loop, and it is what makes a cheap gate possible at all.
4. Fragility inventory
Silent = fails with no error surfaced to the agent or the user.Hop 1 — client ↔ proxy
Hop 2 — proxy ↔ daemon
Three distinct populations of unanswered call, all covered:
Still fragile:
Hop 3 — daemon lifecycle
Hop 4 — bridge ↔ SDK
Hop 5 — SDK internals
5. Where the gates are
Nine of eleven layers have a gate. The two that do not are exactly the two that need a second repo and a human. The plan for both is in
gate-plan.md.
6. Traces already encode the invariants
runTool opens one tool.handler root span per call and every stage underneath inherits it. With RETICLE_TRACE=1 that makes three assertions available from any existing battery run, at the cost of one log parser:
- exactly one root span at
depth: 0per tool call - zero orphan spans — 23 orphans on one healthy run is the documented signature of a hung call
settleTimingalways fires; it sits in afinallyso a thrown call cannot leak a concurrency slot