API reference
The public API, generated from docstrings.
Agent and results
Reusable agent configuration; run() executes one traced session.
agent = Agent(provider=..., instructions="You are a helpful agent.") result = await agent.run("Summarize README.md")
Source code in src/regista/agent.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
resume(trace_path)
async
Continue an interrupted session from its trace.
The recorded prefix replays for $0 — every request hash-verified, recorded tool results served without re-running their effects — and the first request the recording can't answer falls through to this agent's live provider. From there new tool calls execute for real, gated by this agent's policy. A tool call the crash cut short (no recorded result) is re-executed.
Assumes this Agent is configured like the recorded run; any
difference makes the first request diverge, which just means the
whole task re-runs live. Writes a new trace linked to the original
via replay_of.
Source code in src/regista/agent.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | |
run(task)
async
Run one task to completion in a fresh traced session.
Source code in src/regista/agent.py
76 77 78 | |
run_sync(task)
run() for synchronous callers (scripts, notebooks without top-level await).
Source code in src/regista/agent.py
128 129 130 131 132 133 134 135 136 | |
stream(task)
async
Run one task, yielding events as they happen.
Yields TextDelta/ThinkingDelta/ToolCallStarted/ToolCallFinished/
TurnCompleted as the session progresses, then RunCompleted with the
same RunResult run() would return. The trace is identical to a
blocking run — streaming changes when you see things, not what
happened.
Source code in src/regista/agent.py
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | |
What await agent.run(task) returns. trace_path is the receipt:
every detail this summary omits is in the trace.
Source code in src/regista/session.py
33 34 35 36 37 38 39 40 41 42 43 44 45 | |
Tools
Turn a typed function into a tool: @tool or @tool(name=..., ...).
The JSON Schema is generated from the signature; the docstring summary
becomes the description and Args: lines become parameter descriptions.
Unsupported signatures raise ConfigurationError here, at decoration time.
Source code in src/regista/tools/registry.py
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | |
Name → Tool, plus execution with timing and error capture.
Source code in src/regista/tools/registry.py
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | |
execute(name, input, *, tool_use_id='')
async
Execute a tool call, capturing failures as data.
A tool that raises produces is_error=True content the model can
adapt to — only an unknown tool name is a harness fault (ToolError,
raised by get). tool_use_id is unused here; replay's stub
registry keys recorded results on it.
Source code in src/regista/tools/registry.py
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | |
The standard toolset, bound to one environment.
Returns read_file, write_file, list_dir, glob, search_files, shell, and
fetch. Read-only tools are parallel_safe; write_file and shell are not.
Pair with a policy — allow_all plus shell means the model can run
anything your user account can.
Source code in src/regista/tools/builtin.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | |
A connection to one MCP server; an async context manager.
async with MCPServer.stdio("uvx", ["mcp-server-fetch"]) as server: ... agent = Agent(provider=..., instructions=..., tools=await server.tools()) ... await agent.run(task)
The connection must stay open for as long as the agent may call the
tools. prefix namespaces tool names ({prefix}__{name}) so two
servers exporting the same tool name can coexist in one registry.
Source code in src/regista/tools/mcp.py
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | |
http(url, *, headers=None, prefix=None, timeout_s=30.0)
classmethod
A remote server spoken to over streamable HTTP.
Source code in src/regista/tools/mcp.py
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | |
stdio(command, args=None, *, env=None, prefix=None, timeout_s=30.0)
classmethod
A server spawned as a subprocess, spoken to over stdin/stdout.
Source code in src/regista/tools/mcp.py
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | |
tools()
async
List the server's tools, each wrapped as a regista Tool.
Source code in src/regista/tools/mcp.py
155 156 157 158 | |
Replay
Re-run a recorded session; each live request is hash-verified.
mode: "strict" raises ReplayDivergence with a structural diff,
"warn" warns and continues positionally, "hybrid" falls through to
fallback (required) from the first divergence onward.
The replay's own trace lands next to the original unless trace_dir
says otherwise. Returns the replay's RunResult — cost is $0 for every
call served from the recording.
Source code in src/regista/replay.py
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | |
Serves one recorded trace's LLM responses, hash-verified call by call.
Source code in src/regista/providers/replay.py
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | |
stream(request)
async
Synthetic deltas from the recorded response — a replay can drive a live UI exactly like the original run did.
Source code in src/regista/providers/replay.py
124 125 126 127 128 129 130 | |
Evals
Eval/regression runner: assert on outcomes and trace shape, live or for $0.
An :class:EvalSuite is a list of tasks, each with checks — small functions
that inspect the run's :class:~regista.session.RunResult and its
:class:~regista.trace.reader.Trace and return None (pass) or a failure
message. Three ways to run the same suite:
await suite.run(agent)— live sessions, real cost. Development.await suite.record(agent)— live sessions whose traces are saved as fixtures (only when the task passes — a failing fixture is never committed).await suite.replay()— no agent, no keys, $0: each fixture is strictly replayed (any divergence fails the task — the regression signal) and the checks are judged against the recorded run, so cost/turn assertions mean what they meant when the fixture was recorded.
The intended CI shape is one pytest test::
report = await suite.replay()
assert report.passed, f"\n{report}"
Check = Callable[[RunResult, Trace], 'str | None']
module-attribute
A check inspects one run and returns None (pass) or a failure message.
Any function with this shape works; its __name__ labels it in reports.
EvalReport
dataclass
The suite verdict. assert report.passed, f"\n{report}" reads well.
Source code in src/regista/evals.py
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | |
EvalSuite
A named set of tasks judged by the same machinery live or replayed.
Source code in src/regista/evals.py
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | |
record(agent)
async
Run live and save each passing task's trace to its fixture path.
A failing task's fixture is never written — you can't accidentally
commit a regression as the new baseline. Tasks without a trace
path just run.
Source code in src/regista/evals.py
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
replay(*, trace_dir=None)
async
Judge every task against its recorded fixture, for $0.
Each fixture is strictly replayed first — a ReplayDivergence fails
the task, because it means the harness/tool/prompt behavior no longer
reproduces the recording. The checks are then judged against the
recorded run, so cost and turn numbers are the original ones.
Source code in src/regista/evals.py
225 226 227 228 229 230 231 232 233 234 235 236 | |
run(agent)
async
Run every task live against agent and judge the checks.
Source code in src/regista/evals.py
204 205 206 | |
EvalTask
dataclass
One task in a suite: a prompt, its checks, and (optionally) where its
recorded fixture lives for record()/replay().
Source code in src/regista/evals.py
140 141 142 143 144 145 146 147 148 | |
max_cost_usd(limit)
The run cost at most limit dollars (recorded cost, under replay).
Source code in src/regista/evals.py
91 92 93 94 95 96 97 98 99 | |
max_turns_used(limit)
The run took at most limit turns.
Source code in src/regista/evals.py
80 81 82 83 84 85 86 87 88 | |
no_errors()
The trace has no error events and no is_error tool results.
Source code in src/regista/evals.py
125 126 127 128 129 130 131 132 133 134 135 136 137 | |
output_contains(text)
The final output includes text.
Source code in src/regista/evals.py
58 59 60 61 62 63 64 65 66 | |
stop_reason_is(expected)
The run stopped for expected ("completed", "max_turns", …).
Source code in src/regista/evals.py
69 70 71 72 73 74 75 76 77 | |
tool_never_called(name)
No tool.call for name appears in the trace.
Source code in src/regista/evals.py
113 114 115 116 117 118 119 120 121 122 | |
tool_was_called(name)
At least one tool.call for name appears in the trace.
Source code in src/regista/evals.py
102 103 104 105 106 107 108 109 110 | |
Policies
The permission gate: a pure decision function consulted before every tool call.
A policy maps a PermissionRequest to Allow, Deny, or Ask. Deny is data, not an exception — the model receives an error tool_result and adapts. Ask escalates to the Agent's ask_handler; with no handler configured it resolves to deny, so the harness never hangs waiting for input that can't arrive.
Presets beyond allow_all (read_only, workspace) land with the built-in tools.
PermissionRequest
dataclass
Everything a policy (or a human, via Ask) needs to decide.
Source code in src/regista/policy/permissions.py
18 19 20 21 22 23 24 25 26 | |
allow_all()
Permit every tool call. Fine for FakeProvider tests and trusted tools; prefer an asking policy for anything with side effects.
Source code in src/regista/policy/permissions.py
57 58 59 60 61 62 63 64 65 | |
policy_name(policy)
The name recorded in permission.decision events.
Source code in src/regista/policy/permissions.py
51 52 53 54 | |
Ready-made permission policies.
Presets decide by tool name — they pair with the built-in toolset and treat
anything they don't recognize conservatively (deny or ask, never allow).
Ask resolves to deny when no ask_handler is configured, so the safe
default costs nothing to adopt.
For anything richer, write a function: a policy is just
(PermissionRequest) -> Allow | Deny | Ask.
compose(*policies)
Consult policies in order; the first Deny or Ask decides.
Allow requires unanimity, so composing only ever tightens: stack a
targeted deny in front of a permissive preset, e.g.
compose(deny_shell, workspace()).
Source code in src/regista/policy/presets.py
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | |
read_only(*, allow=())
Permit only known read-only built-ins (plus allow extras); deny the rest.
The strictest preset: right for exploration and summarization agents.
Source code in src/regista/policy/presets.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 | |
workspace()
Permit workspace-scoped file tools (reads and writes); ask for everything else — shell, fetch, and custom tools all escalate.
The environment already pins file effects to the workspace root, so this preset draws the line at effects that can leave it.
Source code in src/regista/policy/presets.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | |
Providers
The LLM boundary: one protocol, four implementations.
An adapter's whole job is normalization — wire format, tool-call shape, usage
extraction, retries. Because ReplayProvider implements this same protocol,
deterministic replay needs no special support anywhere else in the harness
(ARCHITECTURE.md §6, §7).
Streaming lands in v0.1 step 12; the protocol grows a stream() method then.
ModelRequest
Bases: BaseModel
A fully-assembled, provider-neutral request — everything the model will see.
Source code in src/regista/providers/base.py
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | |
params = {}
class-attribute
instance-attribute
Provider-specific passthrough, e.g. {"thinking": {"type": "adaptive"}}.
request_hash()
The replay divergence detector: hash of everything deterministic.
The full request is included — if any input that shapes the request changes (a message, a tool schema, a sampling param), the hash changes.
Source code in src/regista/providers/base.py
39 40 41 42 43 44 45 | |
ModelResponse
Bases: BaseModel
A normalized response. raw is the provider's escape hatch and
replayed marks responses served from a recording — both are excluded
from trace serialization so a replay's payload stays byte-identical to
the original's.
Source code in src/regista/providers/base.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | |
model_dump_trace()
The dict recorded in llm.response events (and served back by replay).
Source code in src/regista/providers/base.py
64 65 66 | |
Provider
Bases: Protocol
Anything that can turn a ModelRequest into a ModelResponse.
model is part of the protocol because the model is chosen where the
provider is constructed — the loop only reads it to assemble requests.
Source code in src/regista/providers/base.py
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
stream(request)
Yield deltas as they arrive, then the final ModelResponse last.
Adapters without native streaming can wrap complete() with
regista.streaming.synthetic_deltas.
Source code in src/regista/providers/base.py
85 86 87 88 89 90 91 | |
Provider backed by the Anthropic Messages API.
The model is chosen here, explicitly — regista never defaults a model.
Pass api_key or rely on ANTHROPIC_API_KEY; client injection
exists for tests and custom transports.
Source code in src/regista/providers/anthropic.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | |
Provider for any Chat Completions-compatible endpoint.
api_key is optional because local servers (Ollama, vLLM) don't need
one. The model is chosen here, explicitly — regista never defaults one.
Source code in src/regista/providers/openai_compat.py
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | |
stream(request)
async
SSE streaming: one attempt, no retry loop (a broken stream can't be resumed mid-response). Tool-call arguments arrive in fragments and are assembled per index before parsing.
Source code in src/regista/providers/openai_compat.py
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | |
A scripted provider for tests — public API, not a test-only hack.
Users unit-test their agents with it; contributors test loop changes with it; ~90% of regista's own suite runs on it. Zero network, zero keys, zero cost.
FakeProvider
Serves a scripted sequence of responses and records every request.
Source code in src/regista/providers/fake.py
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | |
text_response(text, *, usage=None)
A final assistant message ending the turn.
Source code in src/regista/providers/fake.py
22 23 24 25 26 27 28 29 | |
tool_use_response(*calls, text='', usage=None)
An assistant message requesting tool calls.
Each call is (tool_use_id, tool_name, input).
Source code in src/regista/providers/fake.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | |
Streaming
The streaming event vocabulary for agent.stream().
Streaming changes when you see things, never what happened: the trace
records only final requests and responses, so a streamed session produces the
same trace — and replays with the same hashes — as a blocking one. Providers
yield TextDelta/ThinkingDelta items followed by their final
ModelResponse; the loop adds the tool and turn events around them.
synthetic_deltas chunks a completed message into deltas — how FakeProvider
and ReplayProvider stream, and the reason replayed sessions can still drive a
live UI.
ProviderDelta = TextDelta | ThinkingDelta
module-attribute
What providers yield before their final ModelResponse.
StreamEvent = TextDelta | ThinkingDelta | ToolCallStarted | ToolCallFinished | TurnCompleted | RunCompleted
module-attribute
Everything agent.stream() can yield.
RunCompleted
dataclass
Always the final event: the same RunResult run() would return.
Source code in src/regista/streaming.py
71 72 73 74 75 | |
TextDelta
dataclass
A fragment of assistant text, in order.
Source code in src/regista/streaming.py
28 29 30 31 32 | |
ThinkingDelta
dataclass
A fragment of extended-thinking content.
Source code in src/regista/streaming.py
35 36 37 38 39 | |
ToolCallFinished
dataclass
A tool call resolved — executed, failed, or denied (is_error).
Source code in src/regista/streaming.py
51 52 53 54 55 56 57 58 | |
ToolCallStarted
dataclass
The model requested a tool call; the gate and execution come next.
Source code in src/regista/streaming.py
42 43 44 45 46 47 48 | |
TurnCompleted
dataclass
One llm round-trip finished; usage and cost are for this turn only.
Source code in src/regista/streaming.py
61 62 63 64 65 66 67 68 | |
synthetic_deltas(message, *, chunk_size=8)
Chunk a completed message into deltas, for providers that don't (or can't) stream natively — FakeProvider and ReplayProvider.
Source code in src/regista/streaming.py
87 88 89 90 91 92 93 94 95 96 | |
Context
Context management: keep the conversation inside the model's budget.
The trigger is provider-reported usage (never a local token estimate): when a
turn's observed input tokens cross max_input_tokens, the oldest messages
are summarized — by the session's own provider — and replaced with a single
summary message, keeping the most recent messages verbatim.
Compaction is itself traced and replayable: the summarization call is a regular llm.request/llm.response pair (with its own request_hash), followed by a context.compaction event. A replayed session re-runs this exact logic and is served the recorded summary, so post-compaction request hashes still match. That is why compaction lives behind the same provider seam as everything else.
ContextConfig
Bases: BaseModel
When and how to compact. Recorded in session.start so replay can reproduce the same compaction points.
Source code in src/regista/context/__init__.py
52 53 54 55 56 57 58 59 60 61 62 63 64 | |
keep_recent_messages = 4
class-attribute
instance-attribute
Messages preserved verbatim at the end of the history.
max_input_tokens = None
class-attribute
instance-attribute
Compact when a turn's observed input tokens reach this; None disables.
compact_history(history, *, provider, context, writer, turn, observed_input_tokens, price_overrides=None)
async
Summarize the old history; returns (new_history, usage, cost) or None if there is nothing safe to drop. Provider errors propagate to the loop.
Source code in src/regista/context/__init__.py
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | |
render_transcript(history)
A deterministic plain-text transcript for the summarizer.
Thinking blocks are excluded — private reasoning is not context the summary should carry forward.
Source code in src/regista/context/__init__.py
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | |
Types
Provider-neutral message vocabulary shared by every module.
The shapes here are a superset of the Anthropic Messages API (role plus a list of typed content blocks). Adapters translate their wire formats into this model, never out of it: rich-to-flat translation (for OpenAI-style APIs) is easy, flat-to-rich is lossy. See ARCHITECTURE.md §7.
This module depends on nothing else in regista.
Message
Bases: BaseModel
One conversation entry: a role and a list of typed content blocks.
Source code in src/regista/types.py
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | |
text()
Concatenated text of all TextBlocks (ignores tool/thinking blocks).
Source code in src/regista/types.py
101 102 103 | |
TextBlock
Bases: BaseModel
Plain assistant or user text.
Source code in src/regista/types.py
30 31 32 33 34 35 36 | |
ThinkingBlock
Bases: BaseModel
Extended-thinking content.
signature is preserved verbatim: the Anthropic API requires it when
thinking blocks are sent back in multi-turn history.
Source code in src/regista/types.py
39 40 41 42 43 44 45 46 47 48 49 50 | |
ToolResultBlock
Bases: BaseModel
The harness reporting a tool's outcome back to the model.
A permission denial or tool failure is carried as is_error=True —
it is data the model can adapt to, not an exception.
Source code in src/regista/types.py
64 65 66 67 68 69 70 71 72 73 74 75 76 | |
ToolSpec
Bases: BaseModel
What the model sees of a tool: name, description, and JSON Schema input.
parallel_safe is harness metadata (read-only tools opt in to concurrent
execution); it is never sent to the model.
Source code in src/regista/types.py
109 110 111 112 113 114 115 116 117 118 119 120 121 | |
ToolUseBlock
Bases: BaseModel
The model requesting a tool call. The harness executes; the model only asks.
Source code in src/regista/types.py
53 54 55 56 57 58 59 60 61 | |
Usage
Bases: BaseModel
Token accounting as reported by the provider (never locally estimated).
Cache fields map from Anthropic's cache_read_input_tokens /
cache_creation_input_tokens; providers without caching leave them 0.
Source code in src/regista/types.py
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
Trace
The versioned trace event schema — regista's most important contract.
Every subsystem writes these events; replay, resume, and OTel export are all consumers of them. The rule (ARCHITECTURE.md §2): if a behavior isn't in the trace, it's a bug — and any schema change requires a SCHEMA_VERSION bump plus a migration note.
LLM requests/responses are stored as plain dicts (the normalized ModelRequest/ ModelResponse dumps) rather than typed models: trace/ sits below providers/ in the dependency graph and must not import from it.
ContextCompaction
Bases: _Event
Compaction changes every subsequent request, so it must be traced.
Source code in src/regista/trace/events.py
115 116 117 118 119 120 121 122 | |
LlmResponse
Bases: _Event
The replay payload: the full normalized response, verbatim.
Source code in src/regista/trace/events.py
77 78 79 80 81 82 83 84 85 86 | |
PermissionDecision
Bases: _Event
ask decisions record their resolution so deny paths replay identically.
Source code in src/regista/trace/events.py
104 105 106 107 108 109 110 111 112 | |
SessionEnd
Bases: _Event
Summary only — replay never needs it, humans always read it first.
Source code in src/regista/trace/events.py
133 134 135 136 137 138 139 140 141 142 | |
SessionStart
Bases: _Event
Everything needed to validate that a replay is configured like the original.
Source code in src/regista/trace/events.py
54 55 56 57 58 59 60 61 62 63 64 65 66 67 | |
context = {}
class-attribute
instance-attribute
ContextConfig dump — replay needs it to compact at the same points.
canonical_hash(payload)
SHA-256 over a canonical JSON serialization (sorted keys, no whitespace).
This is the replay divergence detector: two requests are "the same" iff their canonical hashes match. Callers must exclude nondeterministic fields before hashing.
Source code in src/regista/trace/events.py
26 27 28 29 30 31 32 33 34 | |
Load and inspect recorded traces.
Trace is the read-side twin of TraceWriter and the foundation replay
builds on: it indexes LLM request/response pairs in seq order.
Trace
An in-memory view of one session's JSONL trace file.
Source code in src/regista/trace/reader.py
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
end
property
None if the session crashed before session.end was written.
llm_calls()
Request/response pairs in seq order — the replay index.
Source code in src/regista/trace/reader.py
86 87 88 89 90 91 92 | |
tool_results()
tool_use_id → result, for stubbed-tool replay.
Source code in src/regista/trace/reader.py
94 95 96 | |
OpenTelemetry export: one more consumer of the one trace.
Post-hoc by design — spans are built from recorded events with their recorded timestamps, so exporting yesterday's session (or a $0 replay of it) paints the same picture in Jaeger/Grafana as a live run would have. There is no second instrumentation path to drift out of sync with the JSONL log.
Span hierarchy: session → turn → llm / tool. LLM spans carry gen_ai.*
semantic-convention attributes plus regista extras (request_hash, cost,
replayed). Requires the otel extra: pip install "regista-harness[otel]".
export_trace(trace, *, tracer_provider=None)
Emit one recorded session as a span tree.
Spans go to tracer_provider (or the global one), so any configured
exporter — OTLP, Jaeger, console — receives them.
Source code in src/regista/trace/otel.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | |