Skip to main content

Module opencode

Module opencode 

Source
Expand description

OpenCode: sessions in a SQLite database, not a JSONL log.

Format notes (verified on OpenCode 1.18.15, 2026-09-05, against the live opencode.db on this machine):

  • The store is $XDG_DATA_HOME/opencode/opencode.db (or ~/.local/share/opencode/opencode.db), a WAL SQLite database. agent-top opens it read-only and never writes, which honours the observe-only rule and does not block OpenCode’s own writes.
  • session is one row per conversation and already carries the accounting: directory, agent (the agent type, build / explore / plan), model (a JSON blob {"id","providerID","variant"}), cost (US dollars, computed by OpenCode), tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write, time_created, time_updated (epoch ms), parent_id and version. A subagent is a session row whose parent_id is the parent’s id.
  • Because OpenCode has already priced the session, its cost is used directly rather than re-priced from agent-top’s table: OpenCode runs third-party models (DeepSeek, and so on) that the table does not carry, and the harness’s own figure is the real one. So an OpenCode row’s cost is exact and never a floor, and unpriced_tokens is zero.
  • message is one row per message, data JSON with role (user / assistant) and time {created, completed} in epoch ms. A user message opens a turn; each assistant message is one inference, from created to completed, and extends the turn it belongs to, which ends at the last reply before the next prompt. A reply with no completed is still in flight, so its inference and turn stay open. Assistant messages are also the turn count.
  • part is one row per message part, data JSON with type. A tool part has tool (the name), callID and state with status (completed / error / …) and time {start,end} in epoch ms, which is one tool span. step-start / step-finish, reasoning, text and patch parts are not read.
  • MCP calls (verified on OpenCode 1.18.15, 2026-09-13, with a filesystem server named scratch_fs and a live session): an MCP call is an ordinary tool part whose tool is the server name and the tool name joined by _, each with every character outside [a-zA-Z0-9_-] replaced by _ (scratch_fs_list_directory). There is no mcp prefix and nothing else in the part marks it as MCP; a call the server rejects has status error. So the name alone cannot say where the server ends, and a built-in tool with an underscore would look the same. The server names come from OpenCode’s config instead, key names of mcp only: the global config.json, opencode.json and opencode.jsonc in $XDG_CONFIG_HOME/opencode (or ~/.config/opencode); opencode.jsonc, opencode.json and .opencode/opencode.json[c] in the session directory and each parent up to the worktree root; and ~/.opencode. A tool part is an MCP call when its name starts with a configured server’s sanitised name and _, the longest such name winning. A server configured only through OPENCODE_CONFIG in the agent’s own environment cannot be seen from outside the process and is not counted. Every tool part, MCP or not, is still counted as a tool call and a span.
  • Context by source (verified on OpenCode 1.18.15, 2026-09-13): each assistant message is one model response and carries its own tokens {input, output, reasoning, cache: {read, write}}, cost (US dollars) and modelID. The tool parts of a message are the calls that response made, and the next assistant message’s prompt carries their results. So the ledger is fed per session in time_created order: each response’s usage, then that message’s tool calls as pending results. A compaction is written as a user message with a compaction part followed by an assistant message with summary: true and mode: "compaction"; the ledger resets after that reply, exactly rather than by the prompt-halved fallback. The per-message cost is one total, so the prompt-side share the ledger charges is taken from agent-top’s price table for the model, scaled so the classes add up to OpenCode’s figure; a model the table does not price gets tokens and no cost, which the UI shows as -. Subagent ledgers are folded into the parent’s.

A session has no file of its own, so a tracker is addressed by a virtual path <db>/<session id>: unique, stable, and with the session id as its file stem, which is all the collector and the trace resolver need.

Structs§

ConfigRoots
Where OpenCode’s config lives, for reading MCP server names. The fields are the roots that differ per machine, so a test can point them elsewhere.
OpenCodeAdapter
The OpenCode adapter. See the module notes for the store it reads.
OpenCodeTranscript
One OpenCode session as a SessionSummary, read from the database.
Session
One top-level conversation, enough to attribute it to a process and list it.

Functions§

data_dir
$XDG_DATA_HOME/opencode, or ~/.local/share/opencode.
db_path
The session database, when it exists.
mcp_server_names
The MCP server names configured for a session in directory: the keys of every mcp section OpenCode would read, and nothing else from those files. Sorted and deduplicated.
mcp_server_of
The configured server an OpenCode tool name belongs to, if any. The tool key is <server>_<tool> with both halves sanitised, so a name matches a server when it starts with the sanitised server name and _ and has a tool name after it. Of several matches (github and github_enterprise) the longest is the server.
recent_sessions
Top-level sessions (no parent) written since since, newest activity first.
session_id_of
The session id in a virtual path.
session_path
The virtual path that stands for a session on disk: the database path with the session id appended. Never opened as a file; only its stem is read.