modelmri-record¶
Stdlib only. See Recording agents for the guide; this is the surface.
trace(name, endpoint=..., redact=True)¶
Context manager. Everything recorded inside becomes one trace, delivered on exit.
| argument | default | meaning |
|---|---|---|
name |
— | what the run is called in the viewer |
endpoint |
http://127.0.0.1:5900/api/traces/import |
where to POST |
redact |
True |
True for the default scrubber, a str -> str callable, or False for verbatim |
An exception raised inside is recorded as an error step and then re-raised —
tracing never swallows your errors.
step(kind, name="", ...)¶
Records one step. Usable bare, or as a context manager to nest everything inside it underneath.
| argument | type | notes |
|---|---|---|
kind |
str | one of the ten below |
name |
str | free text; shown on the timeline |
input / output |
any | non-strings are JSON-encoded, falling back to repr |
duration_ms |
int | measured for you when used as a context manager |
tokens_in / tokens_out |
int | None | shown on the step |
error |
bool | marks the step failed |
Outside a trace() it returns a falsy no-op that still supports with, so
instrumentation left in library code costs nothing for callers who never opted
in — including callers on worker threads, where contextvars do not reach.
kind¶
A closed list, mirrored at runtime as modelmri_record.KINDS. The viewer gives
each kind a colour and a shape, groups the timeline by it, and refuses a
document containing one it does not know — the whole document, not the step.
| kind | what it is |
|---|---|
llm_call |
a call to a model |
tool_call |
a tool, a shell command, a function |
subagent |
a nested agent; used as a context manager, everything inside becomes its children |
mcp_call |
a tool reached over MCP, kept apart from tool_call because the transport is its own failure |
user_turn |
a person said something |
error |
a failure worth its own step; also synthesised when an exception escapes trace() |
retrieval |
fetching candidate documents — a vector store, a search index, a grep |
embedding |
text to vector |
rerank |
reordering candidates against the query |
guardrail |
a policy check on the way in or out — not error, since one that fires did its job |
The recorder records a kind it does not recognise anyway, and prints one line saying so. It will not raise, and it will not drop the step.
instrument_anthropic()¶
Wraps anthropic.resources.messages.Messages.create so every call becomes an
llm_call step with model, prompt preview, response and token counts. Returns
False if the anthropic package isn't installed; idempotent.
redact.make_redactor(extra, include_defaults=True)¶
Builds a redactor from your own patterns plus the built-ins.
redact.default_redactor(text) is the built-in scrubber if you want to call it
directly.
Guarantees¶
- It never raises into your app. Unreachable endpoint, read-only disk, unserialisable payload, cyclic object graph — all degrade quietly.
- Credentials are removed before anything leaves the process, including from payloads the recorder itself truncated.
- Delivery is idempotent. The shutdown flush and the normal exit path cannot double-write a run.
- Parentage is per-task. Concurrent asyncio tasks each get their own view of the ancestry, so parallel agents produce a correct tree.
Trace format¶
{
"id": "9f2a1c4de8b7",
"name": "fix-failing-tests",
"started_at": "2026-08-08T09:14:22Z",
"meta": { "recorder": "modelmri-record/0.1.2" },
"steps": [
{ "id": "…", "parent_id": null, "kind": "llm_call", "name": "plan",
"started_ms": 0, "duration_ms": 1200, "input": "…", "output": "…",
"tokens_in": 900, "tokens_out": 200, "error": false }
]
}
POST one of these to /api/traces/import to load a trace recorded elsewhere.