This document describes how OpenCode log files are structured and processed by the dlab connect TUI, dlab timeline, and dlab view commands.
Log parsing is centralized in dlab/opencode_logparser.py, the single source of truth for reading OpenCode NDJSON logs. Its main entry points:
parse_log_file()— parse one log file into a list ofLogEventobjectsbuild_session_graph()— assemble all log files of a work dir into aSessionNodetree (main agent → parallel runs → instances/consolidator)
timeline.py, the connect TUI (dlab/tui/), and the browser viewer (dlab/viewer/) all consume this module rather than parsing logs themselves.
OpenCode writes logs as NDJSON (newline-delimited JSON). Each line is a complete JSON object representing a single event.
_opencode_logs/
├── main.log # Primary agent execution
└── {agent}-parallel-run-{timestamp}/ # Parallel agent execution
├── instance-1.log # First parallel instance
├── instance-2.log # Second parallel instance
├── instance-N.log # Nth parallel instance
└── consolidator.log # Consolidator agent
| Log File Path | Source Name |
|---|---|
main.log |
main |
consolidator.log |
consolidator |
poet-parallel-run-123/instance-1.log |
poet-parallel-run-123/instance-1 |
poet-parallel-run-123/consolidator.log |
poet-parallel-run-123/consolidator |
Marks the beginning of an agent thinking step.
Raw JSON:
{
"type": "step_start",
"timestamp": 1769011728617,
"sessionID": "ses_41eaf371affeev9CR0EqK6e6tZ",
"part": {
"id": "prt_be150d0e8001pkRHPmTX0nfZy0",
"sessionID": "ses_41eaf371affeev9CR0EqK6e6tZ",
"messageID": "msg_be150c9830019jERyh8FsUa7UO",
"type": "step-start"
}
}Key Fields:
timestamp: Unix milliseconds when the step begansessionID: Unique session identifier (format:ses_+ alphanumeric)part.messageID: The message this step belongs to
TUI Display: Step started
Marks the end of an agent thinking step with cost and token usage.
Raw JSON:
{
"type": "step_finish",
"timestamp": 1769011739640,
"sessionID": "ses_41eaf371affeev9CR0EqK6e6tZ",
"part": {
"id": "prt_be150fbf80011hl6ugfdWcY4A4",
"sessionID": "ses_41eaf371affeev9CR0EqK6e6tZ",
"messageID": "msg_be150c9830019jERyh8FsUa7UO",
"type": "step-finish",
"reason": "tool-calls",
"cost": 0.0285735,
"tokens": {
"input": 2,
"output": 166,
"reasoning": 0,
"cache": {
"read": 0,
"write": 6954
}
}
}
}Key Fields:
part.reason: Why the step finished"stop"- Agent completed its task"tool-calls"- Agent is waiting on tool execution"max-tokens"- Hit token limit
part.cost: API cost in dollarspart.tokens: Detailed token breakdown
TUI Display: Step finished ({reason})
Completion Detection: A log is considered complete if the last step_finish has reason: "stop".
Agent text output (reasoning, responses, etc.).
Raw JSON:
{
"type": "text",
"timestamp": 1769011729962,
"sessionID": "ses_41eaf371affeev9CR0EqK6e6tZ",
"part": {
"id": "prt_be150d0ea0010Y9OiknHtifuoI",
"sessionID": "ses_41eaf371affeev9CR0EqK6e6tZ",
"messageID": "msg_be150c9830019jERyh8FsUa7UO",
"type": "text",
"text": "I'll help you create the perfect poem about that beautiful, slightly melancholic bathroom scene. Let me follow my workflow and start by consulting POPO the Poet, the old legend.",
"time": {
"start": 1769011729960,
"end": 1769011729960
}
}
}Key Fields:
part.text: The actual text contentpart.time: When text generation started/ended
TUI Display: Raw text rendered as Markdown (headers, code blocks, lists, etc.)
A tool call made by the agent. Structure varies by tool type.
Raw JSON (task tool):
{
"type": "tool_use",
"timestamp": 1769011739640,
"sessionID": "ses_41eaf371affeev9CR0EqK6e6tZ",
"part": {
"id": "prt_be150d6290010SyS7cG1Bn2g4U",
"sessionID": "ses_41eaf371affeev9CR0EqK6e6tZ",
"messageID": "msg_be150c9830019jERyh8FsUa7UO",
"type": "tool",
"callID": "toolu_01Mg3doaZEHVSd7NXZW73zkq",
"tool": "task",
"state": {
"status": "completed",
"input": {
"subagent_type": "popo-poet",
"description": "POPO writes bathroom poem",
"prompt": "Write a poem about how when the light shines..."
},
"output": "Oh boy, oh BOY! This is gonna be my BEST poem yet!...",
"title": "POPO writes bathroom poem",
"metadata": {
"summary": [],
"sessionId": "ses_41eaf2565ffecbXtwtW3ysNdn7",
"truncated": false
},
"time": {
"start": 1769011731097,
"end": 1769011739639
}
}
}
}Common Fields:
part.tool: Tool name (task,read,write,edit,bash,parallel-agents, etc.)part.callID: Unique identifier (format:toolu_+ alphanumeric)part.state.status:"completed","failed", etc.part.state.input: Parameters passed to the toolpart.state.output: Result from tool executionpart.state.time: Execution timing (start/end in milliseconds)
The TUI (dlab connect) uses the monokai theme with custom hex colors:
| Event Type | Color |
|---|---|
step_start |
Cyan #66D9EF |
step_finish |
Green #A6E22E |
text |
Foreground #F8F8F2 |
tool_use |
Orange #FD971F |
task_start |
Orange #FD971F |
task_finish |
Green #A6E22E |
error |
Red #F92672 |
Agent indicators use text labels instead of emoji (e.g., md, py, csv for file types in the artifacts pane).
| Event Type | Display Format |
|---|---|
step_start |
"Step started" |
step_finish |
"Step finished ({reason})" |
text |
Raw text rendered as Markdown |
tool_use |
Tool-specific (see below) |
| Tool | Display Format | Truncation |
|---|---|---|
bash |
bash: {description or command}--- output ---{output} |
500 chars |
read |
read: {filename} |
- |
write |
write: {filename}{content} |
300 chars (full for .md files) |
edit |
edit: {filename}-{oldString}+{newString} |
100 chars each |
task |
task: {subagent_type} - {description}--- output ---{output} |
1000 chars |
parallel-agents |
parallel-agents: {agent} x{count}--- output ---{output} |
1000 chars |
| Other | {tool} ({status}){output} |
200 chars |
The following content types are rendered as Markdown when expanded:
- All
textevents writetool output for.mdfiles
The TUI shortens long agent names for the sidebar.
The main agent is renamed to include the default agent from opencode.json:
| Original | Display (if default_agent="literary-agent") |
|---|---|
main |
main-literary-agent |
Long parallel run names are compressed:
| Original | Display |
|---|---|
poet-parallel-run-1769011747728/instance-1 |
⟝ poet …28/ inst-1 |
poet-parallel-run-1769011747728/instance-4 |
⟝ poet …28/ inst-4 |
poet-parallel-run-1769011747728/consolidator |
⟝ poet …28/ cnsldtr |
Pattern: ⟝ {agent} …{last 2 digits}/ {shortened suffix}
Suffix transformations:
instance-N→inst-Nconsolidator→cnsldtr
The dlab timeline command builds execution visualizations from logs.
| Function | Purpose |
|---|---|
parse_log_file(path) |
Parse NDJSON, extract events with metadata |
is_log_complete(path) |
Check if last step_finish has reason="stop" |
discover_agents(dir) |
Find agent definitions in .opencode/agents/*.md |
build_timeline(logs_dir) |
Construct timeline with Gantt visualization |
natural_sort_key(name) |
Sort: main → tasks → instances → consolidator |
When agents call task or parallel-agents, the waiting time is tracked:
- Extracted from
state.time.startandstate.time.end - Visualized as grey bars (░) in Gantt charts
- Shows when an agent is blocked waiting on subprocesses
Task subagents (called via task tool) get synthetic timeline entries:
- Appear with
(task)suffix in source name (e.g.,popo-poet (task)) - Two synthetic events:
task_startandtask_finish - Timing extracted from the calling
tool_useevent
- All timestamps are Unix milliseconds (not seconds)
- Convert to datetime:
datetime.fromtimestamp(timestamp / 1000) - Duration calculations:
(end_ms - start_ms) / 1000for seconds
| ID Type | Format | Example |
|---|---|---|
| Session | ses_ + alphanumeric |
ses_41eaf371affeev9CR0EqK6e6tZ |
| Part | prt_ + alphanumeric |
prt_be150d0e8001pkRHPmTX0nfZy0 |
| Message | msg_ + alphanumeric |
msg_be150c9830019jERyh8FsUa7UO |
| Tool Call | toolu_ + alphanumeric |
toolu_01Mg3doaZEHVSd7NXZW73zkq |