Skip to content

Events

Every harness adapter converts its native stream (Codex JSONL, Claude Code stream-json, the generic JSONL protocol, or direct calls from a Python adapter) into one provider-neutral event schema. The dashboard timeline, metrics and aggregation only ever see this schema.

Event

Field Meaning
event_id Time-sortable id.
run_id The run.
sequence 0-based position in the run; unique per run.
timestamp UTC.
kind One of the kinds below.
source runner, codex, claude, generic, harnesslab, …
name Tool or command name, or a system event name.
duration_ms Set on finished events and some system events.
payload Kind-specific JSON, redacted before persistence.
call_id Pairs *_started with *_finished.
parent_call_id Set for subagent activity.
raw_metadata Optional provider metadata, redacted.

Kinds and payload conventions

Kind Payload
run_started task, variant, runner, model, repetition, base_commit, worktree
run_finished status, outcome, error, exit_code; duration_ms is the wall time
assistant_message text (final messages are recorded but are not evidence of success)
tool_started tool, input
tool_finished tool, status (completed, error, interrupted), output
command_started command, optional description
command_finished command, exit_code, output, optional stderr, timed_out, status
file_change path, kind (add, update, delete, …), tool
usage input_tokens, cached_input_tokens, cache_write_tokens, output_tokens, reasoning_output_tokens, model
error message, plus provider fields
reasoning_event count only. Thinking text and signatures are dropped at parse time and never persisted.
system Named events: harness_launch (argv, CLI version, harness hash, applied components), setup_command, verification, api_retry, compact_boundary, permission_denied, harness_components_ignored, hook (a Claude Code hook finished: event, name, exit code, decision, blocked), improve_baseline, improve_round_started, improve_round (improvement tasks), safety (the run's safety summary), non_json_output, unknown_record, …

Started and finished events with the same call_id are paired in the timeline; a started event without a matching finish is closed as interrupted when the run ends and counts as tool_calls_unfinished.

Where events go

Events are buffered by the emitter, redacted (provider API keys, GitHub tokens, bearer tokens, AWS, Slack and Google keys, NAME=value assignments for secret-looking names, private-key blocks and the literal values of secret-looking variables in Harness Lab's own environment), and flushed to the events table in batches while the run is still executing. Real harness runs also keep a sanitized copy of the provider stream as the agent_stream artifact and the CLI's stderr as agent_stderr, both redacted. See Storage and data layout.

The generic JSONL protocol

A custom harness can emit events directly on stdout with output_format: jsonl:

{"kind": "command_started", "call_id": "c1", "payload": {"command": "pytest -q"}}
{"kind": "command_finished", "call_id": "c1", "duration_ms": 1200, "payload": {"exit_code": 0, "output": "5 passed"}}
{"kind": "usage", "payload": {"input_tokens": 1200, "output_tokens": 300}}

kind, name, call_id, duration_ms and payload are honoured; when payload is absent the remaining keys form it. Every usage line adds to the totals and counts as one LLM call.