Install
Instrument
session_id and agent_id. The scopes bind identity on context variables and every event call reads it back, so you never thread ids through your functions.
All three work under async with as well as with.
Nesting agents builds the tree. parent_id and depth are computed from the stack:
How a scope closes
agent() handles exceptions for you:
agent_end, because the dashboard closes the span at agent_end and anything after it is attributed to nothing. A cancellation is not a failure, so cancelled runs do not pollute the errors surface. The exception is always re-raised: a scope never swallows.
The event methods
Fifteen methods in six families. Most come in pairs — you emit the opener, then the closer, and the SDK measures the span between them.Example
A tool-calling loop against the OpenAI API, with no agent framework:docs/manual/examples/.
Threads and async
Context variables propagate into asyncio tasks automatically. They do not propagate into new threads, because a thread starts with an empty context.propagate(), the worker’s events raise a TypeError naming the fix rather than landing on no session. That is deliberate: an event with no session is skipped by ingest and answered 200, which is the silent failure the identity layer exists to prevent.
Instrument a framework without an adapter
Every agent framework gives you the same three seams. Map them and you have a complete trace — the four shipped adapters do nothing more than this.Bracket the run
Bracket each tool
Pair each model call
Why there is no AutoGen adapter
Why there is no AutoGen adapter
autogen-corehas been unmaintained since September 2025.- AG2 exposes no process-wide registration point equivalent to the other frameworks’ hooks, so instrumenting it means wrapping every agent at every construction site.
Going deeper
How the recording actually works. None of it is needed to get started.What a recording looks like, per framework
What a recording looks like, per framework
- LangGraph
- CrewAI
- LlamaIndex
- Pydantic AI
- Custom agents
How a session starts and ends
How a session starts and ends
session_id.Status is derived from the shape of the trace:agent_end for you, and on teardown they close anything still open and mark it incomplete — a crashed run settles as done with a visible gap rather than hanging.interrupt() pauses the run, the root span deliberately stays open, and the resuming call closes it. Both calls are one session.Identity: session_id, agent_id, and who mints them
Identity: session_id, agent_id, and who mints them
session_id and agent_id are optional on every event method. Omitted, they resolve from the enclosing scope:TypeError naming the fix rather than emitting an event with no session, which ingest would skip while answering 200.Scopes bind identity on context variables. Those propagate into asyncio tasks automatically but not into new threads — wrap a worker in failproofai_sdk.propagate().Who mints which id
How adapters resolve session_id
First match wins:- An explicit
session_idoption - Per-call metadata
- The enclosing
session()scope - Framework metadata
- The framework’s own run id
Keep agent_id low cardinality
It is the primary facet on every dashboard surface, and a LowCardinality(String) column. A per-run value degrades the column and fills the filter dropdown with one entry per run.Adapters defend that column for you:fw_agent_id / fw_run_id, where it stays queryable without being a facet.Event types, grouped — and which framework records what
Event types, grouped — and which framework records what
human_pause and human_interrupt describe a person acting on the agent, which no framework signals — emit those yourself.Pairs, correlation and duration
Pairs, correlation and duration
Correlation rules
- Reuse the same
tool_call_id,hook_id,pause_id, orinput_idfor the matching completion event. - The SDK computes
duration_msfortool_result,hook_completed,agent_resume, andhuman_input. Passing it to those methods raisesValueError. duration_msis accepted onmodel_response, because only the caller knows the real provider latency. It must be an integer — a float raisesValueErrorat the call site, because the server reads the column as an unsigned 32-bit integer and would store NULL for anything else.- Correlation keys are scoped by kind and session, so a tool call and a hook may safely share an id, and two concurrent sessions may reuse the same ids without colliding. They are not scoped by agent: a pair opened under one agent and closed under another still correlates, which is the ordinary case in multi-agent frameworks.
request_idpairsmodel_requestwithmodel_response. Without it, model events pair in order per agent, so concurrent calls mispair.- A pair split across processes still correlates downstream, but the SDK cannot compute its in-process duration.
- The pending map holds at most 10,000 starts and evicts the oldest entry when full.
What is in the package, and how instrument() finds your framework
What is in the package, and how instrument() finds your framework
failproofai-sdk installs everything, all four adapters included. The extras pull in the framework, not the adapter.import failproofai_sdk is contractually zero-dependency, enforced by a test that installs the built wheel with --no-deps and another that proves no framework reaches sys.modules.sys.modules, not the installed package list, so a framework you have installed but never imported is not instrumented and is never imported on your behalf. To see what is wired up:instrument("crewai") on a machine without CrewAI does not raise. It logs a warning and returns (), so one missing framework never takes down a process that also instruments others.The warning carries the underlying ImportError, and that message names the exact install command — so the fix is in your logs, not hidden.FAILPROOFAI_SDK_STRICT=1 to have it raise instead. That flag is read once and cached, so export it before your process starts rather than setting it mid-run.How events reach Cloud
How events reach Cloud
.tmp first, then fsync, then an atomic rename:.jsonl, so it can never read a half-written file. The stem carries a timestamp, process id and sequence number, so two processes flushing in the same millisecond cannot collide. The queue is capped at 10,000 events; past that it drops the oldest and logs.The daemon ships your batches. It does not open or rewrite them.ls races the collector and shows a fraction of what you emitted — indistinguishable from an SDK that recorded nothing.To confirm events actually landed, check the dashboard. To watch the spool fill up, stop the daemon first.When instrumentation fails
When instrumentation fails
try and everything the SDK does happens outside it.FAILPROOFAI_SDK_STRICT=1 to make a swallowed failure loud.Common problems
A span never finishes
A span never finishes
model_request with no model_response, or a tool_use with no tool_result. Use the scopes, which guarantee the pair even when the body raises. If you call the event methods directly, use try and finally.Passing duration_ms raises a ValueError
Passing duration_ms raises a ValueError
tool_result, hook_completed, agent_resume, and human_input. It is accepted on model_response, because only you know the real provider latency, and it must be an integer.Events from a worker thread raise a TypeError
Events from a worker thread raise a TypeError
failproofai_sdk.propagate(). See Threads and async.An extra field disappeared or overwrote something
An extra field disappeared or overwrote something
model or outcome would overwrite it and change a stored column. Namespace yours; the adapters use an fw_ prefix.The agent filter has thousands of entries
The agent filter has thousands of entries
agent_id is a low-cardinality facet and you put a run id in it. Use a role or node name and put the real id in a payload field.
