Session inspection
The session stats, session query and session search commands read journaled sessions without resuming them.
stats reports what one session cost and where its time went, query prints the tool calls it made with their
answers, and search finds sessions across the store by agent, model, caller, status, time and prompt.
The commands read the same store as session ls and session show: the file backend under --state-dir by default,
or the backend a configuration names with --config. They read a journal without taking its lock, so they work
against a session that is still running. See Session snapshots for where sessions
are stored.
Note
A session journaled before records carried a time and the agent’s name reports no timing and no agent, and
search --identity leaves it out. Older journals covers both.
This session took six model responses and six tool calls over 22 seconds. One consumer_info call returned an error,
and knowledge_search took the most time. session query shows that call and its answer:
Cost and timing
session stats <id> reports one session: its frame, the tokens of each model response, and a row per tool it called.
| Item | Description |
|---|---|
Agent | The agent identity that started the session, left out when the journal records none |
Caller | Who a channel recorded as asking for the session, left out when there is none |
Model | The model the session ran against |
Status | open, or the reason the session ended |
Created | When the session was created |
Ended | When the session ended, left out for an open session |
Iterations | Model responses across the whole session, against the per-turn cap when one was set |
Wall clock | Creation to the last journal record |
Tokens | Total against the token budget, then the split by tier |
The iteration cap applies to each turn on its own, so a conversation of several turns can show more responses than the cap. The wall clock runs to the last record journaled, so for a chat conversation continued over several days it includes the time between turns.
The token total counts uncached input, output, cache reads and cache writes, weighed the same, which is what a token
budget counts. The Thinking column is the part of Out the model spent reasoning and is not added to the total a
second time.
Tools table
| Column | Description |
|---|---|
Tool | The tool the model named |
Kind | The provider that answered: application, builtin, remote, custom, mcp or unknown |
Calls | Calls the model made to the tool |
Dispatched | Answered calls that were handed to the tool’s provider |
Not run | Answered calls that were not handed to the provider, such as a call a policy denied or the operator refused |
Unanswered | Calls with no result yet |
Errors | Answers that came back as an error |
Time | Total time of the tool’s timed calls |
A result journaled before the provider kind was recorded cannot say whether the call ran. It counts in neither
Dispatched nor Not run, and its kind shows as unknown. One marked as a remote call in that older format shows as
remote and counts as dispatched.
Reading the Time column
A call’s duration is the gap between the record that ended the call and the record journaled immediately before it. The agent dispatches tools one at a time, so no other tool ran in that gap.
The gap also holds anything the run did around the call without journaling it, such as policy hooks and waiting for an operator to answer an approval prompt. A tool behind a confirmation can therefore show the operator’s response time as well as its own.
A call that deferred its answer is timed to the deferral. The time until the answer arrived is not counted, since that depends on whoever supplies it. An approval given while the run was suspended is not counted either.
The Time column sums the durations of a tool’s calls. When only some of the calls could be timed, it says how many
it covers, as in 1.10s (1 of 2 calls), and a dash means it covers none. Lines below the table say why:
| Line | Meaning |
|---|---|
Untimed | Calls that ended with a record carrying no time |
Unanswered | Calls with neither a result nor a deferral, such as one a suspended run left behind |
JSON
--json renders the same report as one JSON document. The example is trimmed to one model response and one tool.
terminal is absent for an open session. duration_ns is in nanoseconds, timed is how many calls it covers, and
untimed counts the calls that ended with no time recorded. undispatched is the Not run column. agent, caller,
updated, ended and each response’s time are left out when the journal does not record them.
Tool calls
session query <id> prints the tool calls of a session in the order they were made, each paired with the answer it
got. With no flags it prints every call.
Each call shows the tool, the call id, the loop iteration that made it and, unless --calls-only is given, its status:
answered, error or no answer yet. A call that deferred its answer also shows a Deferred line with what the tool said it is waiting on.
Input and answers that are JSON are indented, and any other answer, such as a command’s output, prints as it is.
| Flag | Description |
|---|---|
--tool NAME | Selects the calls to this tool, matched exactly; repeat to select several tools |
--iteration N | Selects the calls made at this loop iteration |
--errors | Selects the calls whose answer is an error |
--calls-only | Prints the calls without their answers |
--results-only | Prints only the answers, leaving out calls nothing has answered |
--input-match PATTERN | Selects the calls whose input JSON matches this regular expression |
--output-match PATTERN | Selects the calls whose answer matches this regular expression |
--json | Renders the selected calls as one JSON document |
Every filter given must match. --calls-only and --results-only cannot be combined. --input-match is applied to
the input JSON as journaled, which a model usually sends compact: "consumer":"SHIP matches both consumer_info calls of this session, and a
pattern holding the spaces of the indented form does not. A session with no matching calls prints No matching calls.
JSON
time is when the model response holding the call was journaled and answer_time when the answer was. answer is
the result record as journaled, so content holds the whole answer. A call that deferred carries a
deferred object, and a call with no answer has no answer. --calls-only leaves out answer and answer_time, and
--results-only leaves out input. calls is an empty list when nothing matches.
Finding sessions
session search lists the sessions in the store that match every filter given, newest first. It prints the columns of
session ls, and adds an Agent, Caller or Created column when a filter on that field is given.
| Flag | Description |
|---|---|
--identity AGENT | Selects the sessions of this agent; a session journaled before the agent was recorded carries none and is left out |
--model MODEL | Selects the sessions run against this model |
--caller CALLER | Selects the sessions a channel recorded this caller for |
--status STATUS | Selects the sessions with this status: open, completed, suspended, error, budget or max_iterations |
--since WHEN | Selects the sessions created at or after this time |
--until WHEN | Selects the sessions created before this time |
--match PATTERN | Selects the sessions whose prompt matches this regular expression |
--json | Renders the matching sessions as one JSON document |
--since and --until take a duration counted back from now (24h, 7d), a date taken as local midnight
(2026-09-01), or an RFC3339 time. --status open selects every session with no ending: one still running, one with a
turn in flight, and one that journaled something after it last ended, such as a chat conversation that was continued.
--model, --caller and --identity compare exactly. A search that matches nothing prints No sessions matched.
JSON
This is session search --identity nats-ops --json. status is open or the reason the session ended, and prompt
holds the whole prompt, which the table trims. agent, caller, model and ended are left out when a session
has none. no_agent_excluded counts the sessions --identity left out only because they carry no agent, and is 0
without --identity. runs is an empty list when nothing matches.
Older journals
A journal written before records carried a time and the agent’s name still reads, with less to report.
session stats shows no Agent line, reports the wall clock as not recorded in this journal, and shows - in the
Time column with an Untimed line below the table. Tokens, calls and errors are still reported. In its JSON the
calls count in untimed with duration_ns at 0, and the JSON of session query leaves out time and
answer_time. The file backend reports such a session’s updated time as the journal file’s modification time.
search --identity leaves out every session that carries no agent and says how many it left out:
The agent filter a channel applies to its own listing, such as the web channel’s,
includes a session that carries no agent. --identity excludes it. To see such sessions from the command line, search
without --identity or use session ls.