Query Indexed Facts
Trace Index publishes five domain objects and two text-access relations:
Start by discovering the current binary's contract:
The compact form shows names, SQLite types, and nullability. Load the full schema for one relation when a field's meaning is not yet clear.
Run one bounded, read-only SQL statement:
For multiline SQL, use stdin or a UTF-8 file rather than fragile shell quoting:
Read Semantic values and text references
SQLite stores nested domain values as JSON text. Use SQLite JSON functions instead of treating JSON as an opaque string. Top-level Semantic text is different: value.text, or value.summary for a compaction summary, contains {blob_id} and the bounded text lives in the blobs access relation.
For example, human and Agent text is referenced by the text member selected by the Item's semantic.role:
Use semantic.role, semantic.value, and semantic.evidence_strength for indexed facts. Do not join blobs when the query only needs roles, counts, timestamps, tool metadata, or another non-text value. When the task asks about an exact Runtime field that Semantic does not publish, expand the Item's record_ids and inspect the supporting Record.
Restore one Loop
A Loop is one outer execution lifecycle inside a Session. Items structurally assigned to it have a zero-based loop_position:
Do not infer completion from the last Item. Read the Loop boundary and Runtime-declared outcome:
session_position is the stable Session-level order.
end IS NULL means no ending Record has been observed. A non-null end always contains record_id; its optional outcome member is completed, interrupted, or failed when the Runtime stated a result. A missing outcome does not imply success or failure.
Model identity and token usage belong to the same Loop boundary:
cached and cache_write are already included in input; reasoning is already included in output. Do not add subsets twice. Missing usage means the Runtime did not report enough information, not that the Loop used zero tokens.
Some Items belong to a Session but not to a Loop. For those Items, loop_id and loop_position are both NULL. Query them explicitly when Session-level context matters:
Find what a person asked
Do not infer authorship from a Runtime protocol convention. Trace Index publishes that judgment directly as semantic.role:
human.request opens a Loop. human.steering is human input delivered while a Loop is already open. semantic.evidence_strength = 'structural' means the judgment rests on Runtime structure; heuristic means it rests on a weaker convention and should be reported as such when attribution matters.
Semantic role is also evidence ownership. When a conclusion says that a person requested, decided, approved, rejected, or corrected something, the supporting text must come from a Human Item. An adjacent agent.commentary or agent.final_answer Item shows what the Agent proposed; chronological proximity does not turn that proposal into Human confirmation. If the Human response does not clearly accept or reject the proposal, preserve that distinction instead of silently resolving it.
Report aggregate coverage
Before reporting an aggregate over a scoped Item population, check whether an optional Semantic member or optional relationship prevents some Items from contributing a value. State all three quantities: the intended population, contributors, and missing values. An inner join to the optional value can silently shrink the denominator; missing is not zero, and a contributor count is not a population count. Deliberate search, role, time, and project filters define the scoped population and do not require comparison with excluded Items.
Inspect the evidence for candidate Sessions
Start Session discovery with the narrow facts the question supplies: Runtime, time, working directory, or matching Item text. After identifying candidate Session ids, inspect their published identity, optional relationships, and physical Sources together:
working_directory describes the Runtime context, while each Source locator identifies a physical trace input supporting the Session. These are queryable facts, not an automatic relevance decision. Read a small number of Items relevant to the question from each candidate when the answer depends on what work the Session actually performed.
session_id is an opaque Trace Index-local key intended for joins. native_id is the identity assigned by the Runtime. When the requested deliverable is a list of Codex, Pi, or Claude Code Sessions, report runtime and native_id; include session_id only when it helps continue SQL investigation.
Scope by project and time
Materialize a selective Session set before joining Items:
Times are epoch milliseconds. Use half-open intervals for complete dates. sessions.created_at and items.occurred_at describe domain time; Source synchronization metadata does not prove when an interaction happened.
Every stored time is a UTC instant: the Adapters convert each Runtime timestamp's offset before storing it, and query results return the same milliseconds regardless of the machine that runs the query. A calendar date in a person's time zone is a different boundary from the same date in UTC, so write the boundary with its offset instead of assuming Z:
Keep the instant in machine-facing output and convert only when a person reads the result. Format with the reader's fixed offset and state that offset in the text, for example strftime('%Y-%m-%d %H:%M', occurred_at / 1000, 'unixepoch', '+08:00') reported as 2026-09-07 21:40 +08:00; substitute the offset of the person reading the result. Do not use the SQLite 'localtime' modifier: it reads the time zone of the host running the query, so the same Item renders differently on different machines and the result carries no offset a reader could verify.
Analyze tool calls and outputs as Items
There is no derived tool-call View. Calls and outputs remain separate Items so the timeline stays truthful. A Tool Output points back to its call through semantic.value.call_item_id when the Runtime supplied enough evidence to resolve it.
NULL is an absent Runtime fact, not zero. Report outcome coverage before interpreting failure rates. A call with no linked output may still have happened; the trace may simply lack a resolvable result.
No additional View is needed to find calls that took more than one minute:
This query only includes outputs whose Runtime reported a duration. It does not silently treat unknown durations as zero.
When output size or completeness matters, read both observation boundaries:
source_text_complete = false means Trace Index published only a prefix of the text present in the Source. It is derived from blobs.text and blobs.full_bytes rather than stored independently. runtime_truncated = true means the Runtime had already truncated the original tool output before the Source text was written. Either condition can hold independently.
For a shell-aware call, inspect the nested semantic structure with JSON paths rather than joining permanent syntax Views:
Return to physical evidence only when needed
record_ids is a non-empty JSON array of the physical Records supporting an Item. Expand it only when the task needs provenance:
The records relation contains byte ranges and fingerprints, not raw Runtime JSON. Join sources for the locator. Use record inspect only for an explicit raw-evidence request or index-integrity debugging.
Work within query bounds
Start with counts, groups, identities, and bounded snippets. When complete is false, the returned rows are only part of the result set. Follow the report's hint: narrow the scope, aggregate before fetching rows, return fewer or smaller columns, or continue from a stable, unique ORDER BY key. A nonzero cells_truncated means selected values are prefixes even when all rows were returned. Raise a budget only for selected material that the claim actually needs.
Public relations are the normal facts for analysis. Internal storage tables are implementation details even if SQLite can technically read them.