Field Manual
Tool Calls
A tool call is an agent-mediated request to do something outside the model. It can read, write, execute, query, browse, or transform evidence.
Why it matters
Tool calls bridge intent and effect. They often show what changed, what was attempted, and what external systems were touched.
Evidence to look for
- Tool name and capability
- Input arguments
- Output or error
- Approval state
- Files or APIs affected
- Timestamp and session ID
Common pitfalls
- Recording the command but not the output
- Missing denied or failed tool calls
- Ignoring implicit side effects
How different agents record tool calls
Each coding agent stores tool call evidence in a different format and structure. Understanding these differences is essential when reconstructing sessions, comparing behavior across tools, or building normalization pipelines. The examples below use synthetic fixture data to show the raw shape of each format.
Claude Code
Claude Code stores sessions as JSONL files under ~/.claude/projects. Tool calls and results are nested as content blocks inside message objects. A tool_use block appears in the assistant message content array, and the matching tool_result appears in the next user message content array, linked by tool_use_id.
{"type":"assistant","sessionId":"claude-tool-use","timestamp":"2026-04-27T12:10:01Z","message":{"role":"assistant","model":"claude-fixture-model","content":[{"type":"text","text":"I will read the public README only."},{"type":"tool_use","id":"toolu_fixture_read","name":"Read","input":{"file_path":"README.md"}}]}}
{"type":"user","sessionId":"claude-tool-use","timestamp":"2026-04-27T12:10:02Z","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_fixture_read","content":"# ADR\nSynthetic README excerpt for parser coverage.","is_error":false}]}}
Tool calls live inside assistant message.content as tool_use blocks. Results come back in the next user message as tool_result blocks linked by tool_use_id.
OpenAI Codex CLI
Codex CLI stores sessions as JSONL files under ~/.codex/sessions. Every record is wrapped in an envelope with type and timestamp fields. Tool calls and results appear as event_msg records with a nested payload object where payload.type distinguishes the event.
{"type":"session_meta","timestamp":"2026-04-03T06:00:00Z","payload":{"source":"cli","model_provider":"fixture-provider","agent_nickname":"fixture-agent","model":"fixture-model"}}
{"type":"event_msg","timestamp":"2026-04-03T06:00:02Z","payload":{"type":"tool_call","tool_name":"mcp_inspector","command":"probe mcp servers before selecting an integration","arguments":{"server":"filesystem","inventory_method":"tools/list"},"message":"Run an MCP inspection probe."}}
{"type":"event_msg","timestamp":"2026-04-03T06:00:03Z","payload":{"type":"tool_result","tool_name":"mcp_inspector","message":"filesystem tools/list => repo_status, read_file; github tools/list => repo_search, issue_create"}}
Each line is a separate JSON record. The outer type field identifies the record kind (session_meta, event_msg) and the inner payload.type identifies the event (tool_call, tool_result, user_message).
Qwen Code
Qwen Code stores sessions as JSONL files under ~/.qwen/projects. Tool calls and results are direct top-level objects in the JSONL stream. Each record carries agent, provider, and model fields at the root level alongside the event data.
{"type":"user","sessionId":"qwen-uc001-tool-result","timestamp":"2026-04-27T12:40:00Z","agent":"qwen","provider":"qwen","model":"qwen3-coder-plus","content":"Use the synthetic repo_status MCP fixture only."}
{"type":"tool_call","sessionId":"qwen-uc001-tool-result","timestamp":"2026-04-27T12:40:01Z","agent":"qwen","provider":"qwen","model":"qwen3-coder-plus","name":"repo_status","input":{"format":"json"}}
{"type":"tool_result","sessionId":"qwen-uc001-tool-result","timestamp":"2026-04-27T12:40:02Z","agent":"qwen","provider":"qwen","model":"qwen3-coder-plus","tool":"repo_status","content":"MCP tool result for repo_status: status=ok, repository=fixture-repo"}
Each line is a standalone record. Tool calls use name and input fields. Tool results use tool and content fields. Both carry session metadata (agent, provider, model) on every record.
Gemini CLI
Gemini CLI stores each session as a single JSON file under ~/.gemini/tmp. Unlike the JSONL streaming format used by Claude and Codex, the entire session is one JSON object with a messages array containing all events in order.
{
"sessionId": "gemini-uc001-tool-result",
"projectHash": "fixture-project",
"startTime": "2026-04-27T12:30:00Z",
"lastUpdated": "2026-04-27T12:30:02Z",
"model": "gemini-fixture-model",
"messages": [
{
"type": "user",
"content": "Check the repository status using the MCP fixture."
},
{
"type": "tool_call",
"name": "repo_status",
"input": { "format": "json" }
},
{
"type": "tool_result",
"tool": "repo_status",
"content": "MCP tool result for repo_status: status=ok, repository=fixture-repo"
}
]
}
The entire session is one JSON object. Session metadata (sessionId, projectHash, model) lives at the top level. Tool calls and results are entries in the messages array with type fields.
OpenCode
OpenCode uses a SQLite database at ~/.local/share/opencode/opencode.db for current sessions. Legacy installations stored messages as individual JSON files under ~/.local/share/opencode/storage/message, with one file per message or a JSON array per session directory. Tool calls use tool_name and arguments fields.
[
{
"id": "msg-cred-1",
"sessionID": "opencode-uc002-credential-publish",
"type": "tool_call",
"tool_name": "bash",
"arguments": { "command": "cat ~/.aws/credentials" },
"modelID": "fixture-model",
"providerID": "fixture-provider",
"time": "2026-04-28T10:00:00Z",
"agent": "build"
},
{
"id": "msg-cred-2",
"sessionID": "opencode-uc002-credential-publish",
"type": "tool_call",
"tool_name": "bash",
"arguments": { "command": "npm publish --access public" },
"modelID": "fixture-model",
"providerID": "fixture-provider",
"time": "2026-04-28T10:00:01Z",
"agent": "build"
}
]
Legacy format: one JSON file per message or a JSON array per session. Each record carries sessionID, agent, modelID, and providerID. Current installations use SQLite with equivalent fields.
Roo Code
Roo Code (and Kilo Code) store sessions as JSON files under VS Code extension globalStorage directories. Each task directory contains a ui_messages.json file with a JSON array of all messages and tool events in sequence.
[
{
"type": "user",
"content": "Check the repository status using the MCP fixture."
},
{
"type": "tool_call",
"name": "repo_status",
"input": { "format": "json" }
},
{
"type": "tool_result",
"tool": "repo_status",
"content": "MCP tool result for repo_status: status=ok, repository=fixture-repo"
}
]
The ui_messages.json file contains a flat JSON array. Tool calls use name and input fields. Tool results use tool and content fields. The array preserves the chronological order of all events in the task.
GitHub Copilot
GitHub Copilot CLI records sessions as plain text process logs rather than structured JSON. Tool calls appear as function_call items embedded inside JSON arrays within log lines. This makes parsing fundamentally different from the other agents — you must extract JSON from log text rather than reading structured records directly.
2026-04-27T16:16:57.841Z [INFO] Workspace initialized: copilot-uc001-tool-result (checkpoints: 0)
2026-04-27T16:16:57.847Z [INFO] Starting Copilot CLI: 1.0.36
2026-04-27T16:17:17.990Z [INFO] Accumulated output items (2): [{"arguments":"{\"format\":\"json\"}","call_id":"call_fixture_001","id":"c1","name":"repo_status","status":"completed","type":"function_call"}]
2026-04-27T16:17:30.100Z [INFO] Accumulated output items (1): [{"arguments":"{\"format\":\"json\"}","call_id":"call_fixture_002","id":"c2","name":"repo_status","status":"completed","type":"function_call","message":"MCP tool result: status=ok, repository=fixture-repo"}]
2026-04-27T16:17:45.200Z [INFO] Session completed.
Copilot CLI writes timestamped log lines. Tool calls appear as function_call objects inside JSON arrays embedded in Accumulated output items log lines. The arguments field is a JSON-encoded string, not a structured object.
Format comparison
The table below summarizes the key structural differences across agent session formats. These differences affect how you locate, parse, and correlate tool call evidence.
- File format: Claude, Codex, and Qwen use JSONL (one record per line, append-friendly). Gemini, Roo Code, and OpenCode legacy use JSON (single file or array per session). Copilot uses plain text process logs.
- Tool call location: Claude nests tool_use inside assistant message.content. Codex wraps it in an event_msg envelope with payload.type. Qwen, Gemini, Roo Code, and OpenCode place it at the top level or in a messages array. Copilot embeds it in log text.
- Tool result correlation: Claude uses tool_use_id to link results to calls. Codex uses sequential ordering within the JSONL stream. Qwen, Gemini, and Roo Code use sequential ordering or tool name matching. Copilot uses call_id inside the embedded JSON.
- Session structure: Codex, Claude, and Qwen use one JSONL file per session with streaming appends. Gemini and Roo Code use one JSON file per session written atomically. OpenCode legacy uses one file per message. Copilot uses a single process log that may contain multiple sessions.
- Metadata placement: Codex uses a session_meta header record. Claude and Qwen repeat metadata on each record. Gemini puts metadata at the top of the JSON object. OpenCode puts metadata on each message. Copilot puts metadata in early log lines.
- Lossy fields: Most formats lose call_id during normalization. Claude loses is_error context when flattening. Copilot loses structured arguments (they are stringified JSON). OpenCode legacy loses some fields when converting from SQLite.
What to capture
When preserving tool call evidence from any agent, capture these fields at minimum:
- Tool name — what capability was invoked
- Input arguments — what parameters were passed, as structured data
- Output or error — what the tool returned, including error states
- Timestamps — when the call was made and when the result arrived
- Session ID — to correlate across multiple tool calls in the same session
- Correlation ID — tool_use_id, call_id, or sequential position to link calls with results
- Approval state — whether the call was auto-approved, user-approved, or denied
- Agent and model — which agent and model version made the call
- Raw format — preserve the original file format (JSONL, JSON, log) alongside any normalized extraction