Skip to content

modelmri-record

pip install 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.

from modelmri_record.redact import make_redactor
red = make_redactor([r"ACME-[0-9]{6}"])

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.