Skip to main content
For people working on Reticle — and for an agent asked to explain why a flow behaved the way it did. If you are debugging your app with Reticle, you want usage.md instead.
Reticle produces four different signals and they answer four different questions. Reaching for the wrong one is why investigations here have historically started by reading source.

The daemon log

The daemon writes newline-delimited JSON to:
reticle doctor prints the exact path for your port. Tail it while you work:

Verbose flow tracing

Off by default, because a trace on every tool call is a cost on the hot path — a verification loop is 50–200 calls. Measured, so the claim is checkable: a disabled span costs 126ns per site (against ~9ns for a bare call), which is under a microsecond per tool call and not the literal zero it is tempting to write. Turn it on for the daemon (the flag is read per call, but the daemon is the process doing the work, so it has to be set where the daemon starts):
Every instrumented stage then emits one line when it ends, carrying its own duration:
Read it like this:
  • callId groups every stage of one tool call, and is prefixed with the daemon’s pid so two daemons (or one that restarted) can never claim the same id. Several agents can be inside the daemon at once, so lines interleave — the id is the only thing that makes the output a tree instead of a pile.
  • depth is the nesting level. 0 is the tool handler; anything deeper ran inside it.
  • ms is that stage’s own wall-clock. A parent’s ms includes its children’s.
  • ok:false carries an error field. A stage that threw is still traced — otherwise the trace would show a call that entered a stage and never left, which reads as a hang.
One line per stage, at the end, is deliberate: a start line as well would double the volume to say something the end line already implies.

Where the time goes

The spans that ship today: The first two answer “is this slow because of us or because of the app under test?” browser.command close to tool.handler means the app is taking the time. A large gap between them is Reticle’s own overhead, and that is a performance bug of ours. Filter to one call:
Or find the slowest stages across a session:

The init flow

reticle init is synchronous end to end, so it uses spanSync — same line, same tree:
Two things that trace makes visible and nothing else did. Planning is ~2ms, so init’s wall-clock is essentially all subprocess: the package manager, and claude mcp add when MCP registration is on. And when the pinned install is refused, the unpinned retry is a second full package-manager run — init legitimately takes about twice as long on that path. Every --local fixture run takes it, because the version being installed is not published yet.

Adding a span

Wrap a stage worth a line in a bottleneck hunt — a session resolve, a browser round-trip, a predicate evaluation. Not every function: the trace earns its keep by being faster to read than the code, and that stops being true somewhere around one line per statement. Nesting needs no plumbing — the call id and depth ride in AsyncLocalStorage, so a span added five frames down still lands in the right tree without changing anyone’s signature.
Last modified on August 14, 2026