Full-interaction spans
guard() records tamper-evident step records for individual tool calls. The span API records the whole interaction — user input, retrieval, LLM messages, tool args, assistant output — into the same hash chain, with semantic roles, parent linkage, and optional inline content capture.
It's the right primitive when you want a conversation timeline and aggregate optimization views (slow paths, stuck tools, retrieval quality) without adopting an agent framework.
Three primitives
run(opts, fn)— establishes an implicit run scope. Anything inside the callback inheritsrunIdviaAsyncLocalStorage(JS) orcontextvars(Python). No threading required.span(opts)— record a leaf span at the current scope: role, optional captured content, optionalattrsbag.traced(fn, opts)— wrap a function so its invocation becomes a span. Nestedtraced/spancalls inheritparentStepIdautomatically.
Quickstart
TypeScript
Python
Roles
A span's role drives both dashboard rendering and the cross-run optimization queries.
| Role | When to use |
|---|---|
user | The user's message that started this turn. |
assistant | The model's final text reply to the user. |
system | A system/setup span (e.g., context window injection). Optional — most apps don't emit these. |
llm | A call to a language model. attrs.model is conventional. Content is typically kind: 'messages'. |
tool | A tool invocation that isn't retrieval (writes, planners, browsers, etc.). |
retrieval | Vector / hybrid / FTS / graph search. Content is kind: 'retrieval' with query and results[] — this is what powers the retrieval-quality dashboard view. |
Capture modes
Capture is a deliberate per-span decision. Defaults to hash so existing code is unaffected.
| Mode | Behaviour |
|---|---|
hash (default) | Tamper-evident only. No content stored. Same as legacy guard() records. |
full | Inline raw content. Replayable but stored. Use for non-PII data (policy doc IDs, public planning text). |
full+redact | Inline content after redaction. The unredacted form never enters the hash chain. Fail-closed: throws FuzeError if no redactor is configured. |
sampled | Reserved for future sampling policies. |
The redactor is a single-method interface you supply at configure(...) time:
There is no built-in redactor — the SDK stays neutral about what counts as PII for your domain.
Content shapes
content is a discriminated union keyed by kind. Authoritative schema lives at data/trace-schema.json.
{ kind: 'text', text }— foruser,assistant,systemspans.{ kind: 'messages', messages: [{ role, text }] }— forllmspans.{ kind: 'tool_call', args, result? }— auto-generated bytraced(fn); you rarely emit this manually.{ kind: 'retrieval', query, results: [{ docId, chunkId, score, cited?, snippet? }] }— forretrievalspans. Thecitedflag drives the retrieval-quality scorecard.
attrs is an open record for span-type-specific fields (attrs.model on LLM spans, attrs.jurisdiction on retrieval spans, etc.). Promote frequently-used keys to typed fields in a future revision rather than letting attrs grow load-bearing.
What you get in the dashboard
Once spans are flowing, the cloud dashboard exposes two views you couldn't build before:
/runs/:runId/timeline— a per-run conversation view. Spans render by role (chat bubbles for user/assistant, collapsible message arrays for LLM, score-tagged hits for retrieval). Indented byparentStepId. Errors get a red border./optimization— five panels backed by SQL over the span table: stuck tool calls, runs above P95 step count, slow steps by(role, tool_name), retrieval quality (cited vs uncited scores per jurisdiction), token hotspots. Each row drills into the timeline.
Compliance gating
For tenants in the Cloud or Daemon mode, the ingest endpoint rejects capture !== 'hash' unless organisations.allow_content_capture = true. The default is false, so content can never accidentally enter storage. Operators flip the gate explicitly once data-residency and DPA terms are in place.
Common patterns
Bracketing a conversation turn
Recording a retrieval call with cited results
Wrapping a streaming LLM call
traced() records the span when the wrapped function settles. For streaming calls, record the span at stream-end:
Compatibility
guard(),createRun(),guardMethod,guardedcontinue to work unchanged. The span API is additive.- Pre-v2 records (no
role, nocapture) validate against the new schema after defaults apply (role='tool',capture='hash'). verifyChain()succeeds with mixed pre-v2 and v2 records in the same chain.
Reference
- Wire schema:
data/trace-schema.json - Design rationale:
.context/proposal-full-spans.md - Parity rules (JS ↔ Python):
.context/parity.md