0029. Run records per run in the plant repository, written by core¶
- Status: Proposed
- Date: 2026-10-27
- Amended 2026-10-28:
toolspins every software version of a run, all optional so earlier records stay valid:core_commitalso for an editable or local install (HEAD of the source checkout, afterdirect_url.json) withcore_dirty,pydexpi,claude_code(the transcript'sversion, elseclaude --version),claude_agent_sdk(--sdk-version) andrunner(--runner-name,--runner-commit,--runner-dirty). A null value never makes a record incomplete. - Amended 2026-10-28 (monorepo): the agent runner lives in
agent-cli/of this repository.core_commitand the runner commit are still recorded independently, each from the source that was installed or executed;same_revisionsays whether they are one clean commit, and isfalsewhen either checkout is dirty.
Context¶
Design sessions on a plant repository (interactive Claude Code sessions and headless agent runs)
left their traces in build/agent-run/<stage>/ (ignored) and, by hand, in a per-stage folder
under reviews/<date>-agent-design-run/ (docs/agent-design-run.md). The cost, the turns, the
commits a session started and ended on, and what it was asked were therefore lost for
interactive sessions and copied by hand, per stage, for agent runs; a stage with a revision had
no place for its second session. The process library decided a format for these records
(ChemE-as-Code/process-library ADR 0020, docs/run-records.md): one folder per run, not per
stage, runs/<run-id>/ with run.yaml (schema process-run/v1), task.md, an optional
report.md and the gzipped session transcript; unknown values null, never guessed; a revision
is a new run. Every plant repository needs the writer, so it belongs to core, which ships the
project template and its hooks.
Options considered: a writer in the process library (only its plants would record); a script
in the template (stdlib only, copied into every plant, hard to update); a cheme command,
updated with the tools, called by Claude Code hooks for interactive sessions and by runners for
headless ones.
Decision¶
- cheme-as-code owns the run-record format and its writer.
runs/<run-id>/run.yamlfollowschemeascode.foundation.schemas.run_record.RunRecord(cheme schema process-run);run_idis the start in UTC,YYYYMMDD-HHMMSS, optionally-<slug>. A value nobody knows is null;completeis false when a fact the recorder looks for (start and end commit, start and finish, session id, turns and tokens, the task, the transcript in the folder) is missing, andincomplete_notessays which and why. A cost or budget that the caller does not know is null without making the record incomplete; a cost is never estimated (cost_source: apionly when the caller passed the API's figure). cheme runs startwrites the start of a session (HEAD, branch, time, a digest of the working tree) tobuild/run-records/<session id>.start.json;cheme runs recordcomposes the record from it (or--start-commit), the Claude Code transcript (integrations/claude_code.py, tolerant of unknown lines: each API message's usage counted once, bymessage.id) and the caller's facts, and writes the folder.--checksrecordscheme checkandcheme maturity(run in-process) after the run;--commitcommits the run folder alone.cheme runs listreads the records. An existing folder is refused: records are never edited.- The project template's
.claude/settings.jsonrunscheme runs start --hookonSessionStartandcheme runs record --hookonSessionEnd. A hook never fails the session (any error: one line on stderr, exit 0), never commits, never runs checks, and records nothing when HEAD and the working tree are as at the start, or whenCHEME_RUN_RECORD=off(set by a runner that records its sessions itself).scripts/agent-run/run_stage.pyrecords each session with--kind agent --checks --commit --runner-name run_stage.py. - The transcript is published only when it is safe to: holding
sk-ant-or the value ofANTHROPIC_API_KEYit is not exported at all; after WebFetch, WebSearch,curlorwgetit is written to the ignoredbuild/run-records/instead of the run folder. Otherwise it is gzipped with mtime 0 and its sha256 recorded.
Consequences¶
- Positive: every session that changed a plant repository leaves a record, interactive ones
included, with what it cost and on which commits it started and ended; the format has one
owner and one writer, updated with
uv tool upgrade; a runner needs one command. - Positive: a record is reproducible from its inputs (the gzip carries no time stamp) and never rewritten, so the history of runs is append-only in git.
- Negative: interactive records stay uncommitted until the person commits them; a session that
resumes an earlier one is recorded from its resumption, with the transcript lines before it
left out. A cost is known only for headless runs (the API's
total_cost_usd). - Negative:
--commitruns the repository's pre-commit hook like any commit; a hook that refuses (an invalid P&ID) leaves the record uncommitted and the command exits 1. - Known limitations: Claude Code's transcript format is not a published contract; the reader
skips what it does not understand and reports the count as incomplete.
effortis recorded only when the caller passes it. Publication excludesruns/**in the process library.
Evidence¶
src/chemeascode/foundation/schemas/run_record.py,src/chemeascode/integrations/claude_code.py,src/chemeascode/project/run_records.py,src/chemeascode/interfaces/cli/runs.py.src/chemeascode/templates/project/dot-claude/settings.json(hooks),src/chemeascode/templates/project/scripts/agent-run/run_stage.py,src/chemeascode/templates/project/runs/README.md,docs/agent-design-run.md.tests/test_run_records.py,tests/test_agent_run.py.