Event Stream
The unified NDJSON event format that all agents are translated into
Harness translates every agent's native output into a unified NDJSON event stream. Each line of output is a JSON object with a type field and a timestamp_ms field.
Event types
There are 7 event types:
SessionStart
Emitted once at the beginning of a run.
{
"type": "SessionStart",
"timestamp_ms": 1700000000000,
"session_id": "abc-123",
"agent": "claude",
"model": "claude-sonnet-4-5-20250929",
"cwd": "/path/to/project"
}TextDelta
A streaming text chunk from the agent.
{
"type": "TextDelta",
"timestamp_ms": 1700000000100,
"text": "I'll start by reading"
}Message
A complete message from any role.
{
"type": "Message",
"timestamp_ms": 1700000000200,
"role": "assistant",
"text": "I found the bug in the authentication module."
}Role is one of: assistant, user, system.
ToolStart
A tool invocation is beginning.
{
"type": "ToolStart",
"timestamp_ms": 1700000000300,
"call_id": "call_1",
"tool_name": "read_file",
"input": "{\"path\": \"src/main.rs\"}"
}ToolEnd
A tool invocation has completed.
{
"type": "ToolEnd",
"timestamp_ms": 1700000000400,
"call_id": "call_1",
"success": true,
"output": "fn main() {\n println!(\"hello\");\n}"
}Result
The run has finished.
{
"type": "Result",
"timestamp_ms": 1700000005000,
"success": true,
"text": "Task completed successfully.",
"duration_ms": 5000,
"total_cost_usd": 0.05
}Error
An error occurred.
{
"type": "Error",
"timestamp_ms": 1700000005000,
"message": "Agent process exited with code 1",
"code": "process_error"
}Timestamps
All events include timestamp_ms — milliseconds since the Unix epoch. This allows you to compute durations between events, measure tool execution time, etc.
Consuming the stream
The NDJSON format makes it easy to consume line-by-line:
# Pipe to jq for pretty-printing
harness run --agent claude --prompt "hello" | jq .
# Filter for specific event types
harness run --agent claude --prompt "hello" | jq 'select(.type == "Message")'
# Extract just the text
harness run --agent claude --prompt "hello" | jq -r 'select(.type == "TextDelta") | .text'In code, read line by line and parse each as JSON:
import json
import subprocess
proc = subprocess.Popen(
["harness", "run", "--agent", "claude", "--prompt", "hello"],
stdout=subprocess.PIPE,
text=True,
)
for line in proc.stdout:
event = json.loads(line)
if event["type"] == "TextDelta":
print(event["text"], end="")Session logging
Every run is automatically logged to ~/.local/share/harness/sessions/<id>.ndjson with a metadata sidecar file. You can replay or analyze past sessions from these files.