Trace — debugging what actually happened¶
Every Arcana run writes a JSONL trace file to ./traces/<run_id>.jsonl.
Unlike a log, a trace is structured evidence: each LLM turn carries its
own context decision, prompt snapshot (opt-in), assistant-output digest
by default, tool-call outcomes, and runtime verdict, joined by explicit
causal links.
Treat trace files as records that may outlive the debugging session. They can become audit, compliance, discovery, or legal evidence. Arcana therefore defaults to evidence-shaped, content-minimized trace events: structure, timestamps, causal links, token/cost usage, tool names, and cryptographic digests are retained, while raw prompts, assistant prose, thinking text, tool arguments, and final answers require explicit opt-in.
This page shows the debug loop: one question → one command.
TL;DR — the four commands¶
| Question | Command |
|---|---|
| What happened in turn N? | arcana trace explain <run_id> --turn N |
| What's the shape of this run? | arcana trace flow <run_id> |
| What went wrong and where? | arcana trace show <run_id> --errors --explain |
| List runs I have on disk | arcana trace list |
Pair any of them with --dir /some/path to point at a non-default trace
directory, and --json (on explain / flow) to pipe into tooling.
Turn on dev mode¶
During development, turn on full capture — the trace records the exact
prompt sent to each LLM call and raw turn content so explain can replay
offline:
runtime = arcana.Runtime(
providers={"deepseek": "sk-xxx"},
tools=[...],
dev_mode=True, # implies trace_include_prompt_snapshots=True
trace_dir="./traces",
)
dev_mode is False by default because prompt snapshots can contain
PII / secrets and inflate trace files. It also enables raw turn content
capture. Leave it off in production; rely on
trace_include_prompt_snapshots=True and
trace_include_turn_content=True only where legal & safe.
For production, keep the defaults:
runtime = arcana.Runtime(
providers={"deepseek": "sk-xxx"},
trace=True,
config=arcana.RuntimeConfig(
trace_include_prompt_snapshots=False,
trace_include_turn_content=False,
),
)
With those defaults, TURN events retain digests and lengths for assistant
text, thinking text, tool-call arguments, provider metadata, and final
answers, but not the raw content itself.
arcana trace explain — the full story of one turn¶
$ arcana trace explain 7a3b-… --turn 2
Turn 2 — run 7a3b-… · model deepseek-chat
step_id=9f01-…
Inputs
messages: 8 → 6 (2 compressed) · tokens: 2450 → 1820
[PIN 3] user 120 tokens (kept at L0)
prompt: 6 messages, 4 tools available
[-3] user: what's the current weather in Tokyo
[-2] assistant: I need to look that up
[-1] tool: {"temp_c": 18, "condition": "cloudy"}
LLM output
thinking: <content minimized; digest available>
text: <content minimized; digest available>
tool_calls: 0
Tool results (0)
Runtime verdict
completed: True failed: False
confidence: 0.92
completion_reason: answer provided with tool-verified data
Everything you need to judge that single turn is in one place: the inputs (curated messages + context decision), the raw LLM output, tool outcomes, and the runtime's verdict. No chasing through a log.
Without dev_mode=True, the "prompt: ..." preview falls back to a hint,
and TURN text fields show only minimized evidence. explain still works;
it just does not have raw prompt/output text unless the relevant opt-in flags
were enabled.
arcana trace flow — the causal spine¶
$ arcana trace flow 7a3b-…
Flow — run 7a3b-… · 3 turns
Turn 1 deepseek-chat
├─ ✓ web_search 812ms
│
Turn 2 deepseek-chat
├─ ✓ get_weather 340ms
│
Turn 3 (completed) deepseek-chat
→ stop: completed
Follow the parent_step_id links to see exactly which tools each turn
fired, in order. When a tool call failed it shows ✗, and the turn that
completed the run is tagged.
arcana trace show --errors --explain — triage¶
When something broke, this is your first stop:
$ arcana trace show 7a3b-… --errors --explain
Trace: 7a3b-…
Events: 1 (filtered from 47)
1. error 2026-04-22T10:25:44 None
━━ Error at turn 2 ━━
Turn 2 — run 7a3b-… · model deepseek-chat
…full explain view for the failing turn…
Each error event is resolved to its turn (via parent_step_id) and the
full explain view for that turn is printed inline. No error bouncing
between grep windows.
What's in a trace¶
| Event type | Written by | Links to |
|---|---|---|
turn |
ConversationAgent | previous turn (spine) |
context_decision |
WorkingSetBuilder | current turn |
prompt_snapshot (opt-in) |
ConversationAgent | current turn |
tool_call |
ToolGateway | triggering turn |
cognitive_primitive |
CognitiveHandler | current turn |
llm_call |
OpenAI-compat provider | — |
error |
runtime | offending turn (when applicable) |
The parent_step_id field on every event is what makes flow and
explain possible without heuristics.
Using the reader from Python¶
For custom analysis or CI gates:
from arcana.trace.reader import TraceReader
reader = TraceReader(trace_dir="./traces")
bundle = reader.collect_turn(run_id, turn=2)
print(bundle["turn_event"].metadata["assessment"])
for tc in bundle["tool_calls"]:
print(tc.tool_call.name, tc.tool_call.error)
collect_turn returns a dict with keys: turn_event, context_decision,
prompt_snapshot, tool_calls, cognitive, errors, all.
Pool traces¶
For multi-agent pools (runtime.collaborate()), every event carries a
metadata["source_agent"] tag. Scope any of the above with --agent:
arcana trace explain <run_id> --agent researcher --turn 3
arcana trace show <run_id> --agent critic --errors --explain
See docs/guide/multi-agent.md for the full pool model.