Run

March 29, 2026 · View on GitHub

What a run is

A run is an immutable record of a single attempt to fulfill a goal. Once a run reaches a terminal status, its record is never modified. Annotations (operator notes, post-hoc analysis) are separate files, never edits to the run record.

Schema

See schema/run.schema.json.

Required fields

FieldTypeDescription
idstringN-slug-rN format. System-assigned.
goalstringN-slug format. The goal this run attempts.
plantstringThe plant that executed this run.
statusstringCurrent run status. Terminal statuses are immutable.
started_atISO 8601 UTCWritten before subprocess launches.
driverstringExecution driver (e.g. "codex" or "claude").
modelstringModel identifier.

Conditionally required fields

FieldRequired when
completed_atStatus is terminal
costStatus is terminal
failure_reasonStatus is failure, killed, timeout, or zero_output
reflectionStatus is success AND goal type requires it (see below)

Optional fields

outputs, num_turns, worktree_baseline

Run ID format

N-slug-rN

Where N-slug is the goal ID and N is the attempt number (starting at 1). A goal's first run is 42-fix-the-thing-r1. A retry is 42-fix-the-thing-r2.

Statuses

StatusTerminal?Description
runningNoSubprocess is live
successYesRun completed
failureYesRun failed
killedYesStopped by watchdog or operator
timeoutYesExceeded time limit
zero_outputYesCompleted but produced no output

Cost

Cost is always recorded for terminal runs. The source field is required:

SourceMeaning
providerReported directly by the API
estimatedComputed locally from token counts
unknownDriver did not report cost

unknown is a named state, not an absence. A run whose cost cannot be determined must record {"source": "unknown"}, not omit the field.

Reflection

Reflection is required for successful runs of goal types build, fix, evaluate, and tend. It is the agent's answer to "what was learned?" — not a summary of what was done.

This requirement cannot be enforced by the run schema alone (the run doesn't know the goal type). It is enforced by validate_run_close() in system/validate.py, which cross-references the goal's type at close time.

Goal types spike and research are exempt — they produce artifacts instead.

Run directory

Each run has its own directory at <runtime-root>/runs/<run-id>/:

<runtime-root>/runs/<run-id>/
  meta.json       ← this record (managed by the system)
  events.jsonl    ← raw agent output stream (written by the driver)

meta.json is written by the system at run start and finalized at close. The agent never writes to meta.json. events.jsonl captures the raw event stream from the agent subprocess, from which cost, reflection, and transcript are derived.

For non-converse runs in a readable git worktree, meta.json may also include a system-managed worktree_baseline captured before the run directory or coordinator event files are created. It records:

  • tracked_dirty_paths: tracked files that were already dirty at run start
  • untracked_dirty_count: how many untracked paths already existed
  • untracked_dirty_roots: the distinct top-level roots represented in that untracked set

This is intentionally path-level provenance only. It helps later review answer "was system/cli.py already dirty before this run?" without claiming automatic hunk attribution or a full authored-vs-runtime classifier.

In the current first runtime-auto-commit slice, split-runtime-root gardens also get one system-managed provenance artifact at close:

<runtime-root>/history/commits/<run-id>.json

That record captures the authored HEAD commit and whether authored paths outside the runtime root were clean or dirty when the run closed. The system then commits the runtime tree inside the runtime root's nested git repo. This does not modify meta.json after terminal close; the run record remains immutable.

The close helper publishes these exact bounded outcome codes for the slice:

Outcome codeContract
committedSplit-root capture succeeded: the provenance record was written and the runtime root's nested git history advanced.
runtime_root_not_splitThe garden is using the legacy unified root, so this slice is intentionally inactive and no runtime-history capture is attempted.
runtime_root_outside_gardenThe configured runtime root could not be related back to the garden root, so the slice stops before reading authored provenance.
authored_repo_unavailableThe authored repo HEAD or authored-tree clean/dirty probe could not be read, so no runtime-history record is written.
record_write_failedSplit-root capture started, but writing <runtime-root>/history/commits/<run-id>.json failed, so no runtime commit follows.
runtime_repo_init_failedThe provenance record was written, but creating or configuring the runtime root's nested git repo failed.
runtime_repo_stage_failedThe provenance record was written and the runtime repo exists, but staging the runtime tree failed.
no_runtime_changesThe provenance record was written and staging succeeded, but the runtime repo had no new diff to commit for this capture.
runtime_repo_diff_failedStaging succeeded, but the cached-diff check failed unexpectedly before commit.
runtime_repo_commit_failedThe runtime-history commit command failed after staging detected changes.
runtime_repo_head_unavailableThe runtime-history commit succeeded, but the runtime repo HEAD could not be read back to publish the commit id.

Watchdog

A run in running status is considered alive by the mtime of <runtime-root>/runs/<run-id>/events.jsonl when that file exists. Before the file exists, the watchdog falls back to started_at. If the silence exceeds the configured watchdog interval, the run is transitioned to killed on the next reconciliation pass. The failure_reason is set to "killed".

Failure modes

Reason codeWhen it occurs
MISSING_REQUIRED_FIELDA required field is absent
INVALID_ID_FORMATid does not match N-slug-rN pattern
INVALID_GOAL_FORMATgoal does not match N-slug pattern
INVALID_PLANT_NAMEplant does not match name pattern
INVALID_STATUSstatus is not a valid run status
INVALID_TIMESTAMPstarted_at or completed_at is not ISO 8601 UTC
MISSING_COMPLETED_ATTerminal run missing completed_at
MISSING_COSTTerminal run missing cost
INVALID_COST_SOURCEcost.source is not provider, estimated, or unknown
INVALID_COST_FIELDA cost field has a negative value
MISSING_FAILURE_REASONFailed run missing failure_reason
INVALID_FAILURE_REASONfailure_reason is not a valid reason code
MISSING_REFLECTIONSuccessful run of reflection-required type missing reflection
INVALID_SHAPEDocument is not a JSON object

Validation

  • system/validate.py:validate_run(data) — called at run start and at close.
  • system/validate.py:validate_run_close(run, goal_type) — called at close only. Cross-references goal type to enforce reflection requirement.

Tests

tests/test_validate.pyTestValidateRun and TestValidateRunClose classes.