Skip to content

Tool traces

Every tool call produces two activity frames: tool.call (when the model issues the call, before dispatch) and tool.result (when the dispatched call returns). Both are persisted in the session's activity log and streamed to subscribers.

Frame shape for tool calls

tool.call frame:

ts
{
  kind: 'tool.call',
  phase: 'start',
  requestId: '...',
  seq: <monotonic>,
  ts: <epoch-ms>,
  target: 'system.registry',     // the invoked mount
  op: 'package.list',            // the invoked op
  data: { args: { /* model-supplied JSON */ }, toolCallId: '...' },
  traceId: '...',
  spanId: '...',
}

tool.result frame (one per matching tool.call):

ts
{
  kind: 'tool.result',
  phase: 'complete' | 'error',
  requestId: '...',
  seq: <monotonic>,
  ts: <epoch-ms>,
  target: 'system.registry',
  op: 'package.list',
  data: { result: <truncated>, toolCallId: '...' },
  durationMs: 12,
  traceId: '...',
  spanId: '...',
  parentSpanId: '<tool.call spanId>',
}

OracleSink — the emitter

OracleSink (oracle/OracleSink.ts) is the receiver of tool-timing events from the wrapped tool. The agent passes a callback when constructing the sink; that callback is what turns sink ops into activity frames in InternalOracleRuntimeAdapter.

ts
const sink = new OracleSink(env.prompt.requestId, (op, payload) => {
  this._handleSinkEvent(active, op, payload);  // → activity frame
});

Every wrapped tool calls sink.beforeCall(name, args) and sink.afterCall(name, result|error, durationMs). Those become the two frames.

What gets persisted

Same path as all activity frames (IAgentSessionStore.appendActivityFrames):

  1. The Internal/Claude/Codex adapter accumulates frames per request.
  2. On completion (or periodically for long-running calls) it appends them.
  3. The active session-store backend (FileAgentSessionStore or RecordBackedSessionStore) writes them.

Frames are also published live to activityTo[] mounts so subscribers (Director, Chat) see them as they happen.

Querying tool traces

Two surfaces today:

OpMountReturns
$traceQuerysystem.agentsactivity frames for (sessionId, requestId?)
tool_traces query (in system.observability)system.observabilityobservability-side trace records

The session-side log is the canonical view from the agent platform. Observability is the cross-cutting view that includes non-agent traces too.

Truncation

data.result in a tool.result frame may be truncated. The session message row (session_messages.tool_result) is a JSON column with the full payload — but for activity frames, the convention is to summarize large results (the actual cap is enforced per-tool by the MatrixToolProjection.resultMode).

What's not yet wired

  • Tool replay UI. A Director view that can re-run a tool call from a frame is filed as part of the cognitive readiness panel (P1.12).
  • Tool-call diffing. Frames carry args and results; a UI that diffs across runs is target state.

See also

Source: projects/matrix-3/packages/agents/src/oracle/OracleSink.ts, projects/matrix-3/packages/agents/src/runtime/InternalOracleRuntimeAdapter.ts:71-80 (sink → frame wiring).