Skip to content

story-011: Add execution-history.json written from the same path as events.log - #10

Merged
jerodw merged 2 commits into
mainfrom
story/story-011
Aug 7, 2026
Merged

story-011: Add execution-history.json written from the same path as events.log#10
jerodw merged 2 commits into
mainfrom
story/story-011

Conversation

@jerodw

@jerodw jerodw commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary

A run's history existed only as events.log, a human-readable stream. state.json says what is true now; the history says how the run got there — and an assist agent debugging a run today has to parse log lines to read it. This story adds execution-history.json beside events.log, not in place of it: the structured rendering of the same events, written by the same call.

append_event(run_dir, message, *, kind, stage, artifacts, duration_seconds, verifier_outcome, retry_decision, retry_reason) keeps the prose message positional and still builds the events.log line from that message alone, then appends one structured entry to the new artifact. One write path is the whole design: a second one, however correct, is the drift this exists to prevent.

events.log's line format is frozen — [%Y-%m-%d %H:%M:%S] <message>, byte-identical at every existing call site. It is what l5-status reads and what the appendix documents, so freezing it is a requirement rather than a preference.

History is evidence, never state. Nothing reads execution-history.json to route; load_history is called only by append_event, for the next sequence number.

Changes

  • orchestration/story_coordinator.pyappend_event gains keyword-only structured fields and the second output; load_history is the read side. run_story captures stage_started_at = time.monotonic() where the stage-started event is already appended and reads it through a local elapsed() at every event that ends the stage, so a completed stage carries a duration the log only made derivable. _escalate takes **event_fields and forwards whatever structured values an escalation has, so a run that failed is as reconstructable as one that passed. Every existing call site supplies values: started, completed, verification passed, verification failed with a retry rerouted, all three escalation paths, resumed, story completed, and the stage-exception applied event.
  • schemas/execution-history.schema.json (new) — a top-level array of entry objects. sequence, timestamp, event, message are required; stage, artifacts, duration_seconds, verifier_outcome, retry_decision, retry_reason appear only on the events that have them. Written inside the subset schema_validator supports (type, required, properties, items, enum) — optional fields are expressed by absence from required, not by a union keyword, since the validator has none.
  • tests/test_story_011_validation.py (new, tester stage) — the line-for-entry correspondence over full happy-path, retry-then-pass and escalated runs; the frozen events.log format and unchanged l5-status render, both compared differentially against real pre-story coordinator source; schema conformance for all three run shapes; and the unchanged routing.
  • tests/test_schema_validator.py, tests/test_story_004_validation.py — each adds execution-history to its shipped-schema inventory. Both still assert exact set equality over schemas/; neither assertion was relaxed to a subset.
  • .harness/docs/ARCHITECTURE.md — records the artifact in the run-directory anatomy, the one-write-path and frozen-format rules, why a coordinator-written artifact still gets a schema but no run-time self-check, and why execution-history.json is deliberately not archived under attempts/attempt-N/ (it is not a stage output, so it stays one continuous stream across every attempt).

An entry's artifacts come off stage.get("outputs", []) in the loaded workflow definition, never a list written into orchestration code — the same rule that already governs blocked paths, ownership prefixes, and archivable_artifacts.

Testing

424 passed on .venv/bin/python -m pytest tests/ -q, run from a clean tree at the head of this branch.

Notes for review

  • This story retried once, and the retry is worth reading. The first attempt's tests resolved the pre-story coordinator baseline as git show HEAD:orchestration/story_coordinator.py. That passes while the working tree is uncommitted — and the coordinator commits the tree at the end of a successful run, at which point HEAD is this story's code and 25 tests error. The verifier caught it by committing a copy of the tree and re-running: 394 passed / 25 errors against 419 passed uncommitted. The retry replaced the baseline with pre_story_coordinator_source(), which walks git log --format=%H -- <path> for the newest revision whose blob lacks execution-history and raises loudly when none exists — a search that survives a rebase or squash merge, which a pinned SHA would not. It carries a positive guard asserting the resolved baseline really is the older implementation. ARCHITECTURE.md records this as a general rule for differential tests. The superseded attempt's artifacts are under .harness/runs/story-011/attempts/attempt-1/ (story-010's archiving, first used in anger here).
  • One accepted low finding. The story asked that the implementer's changed-files.json list no path under tests/ at all; it lists three. tests/test_story_011_validation.py is there because the retry's authorized scope was exactly that file, and the other two are the schema-inventory updates described above. The workflow's enforced rule is may_not_create: ["tests/"], and created holds only the new schema, so the coordinator's own rule was satisfied — the verifier diffed both inventory tests and confirmed no assertion was weakened. Recorded in verification-result.json under unverified rather than waved through.
  • l5-status was verified through run_status.format_detail/format_listing and the scripts/l5-status entry point in tests, not by hand in a terminal. run_status.py is unmodified and reads only state.json and events.log, neither of which changed shape.
  • Adding a schema file changes what context_assembler.schema_context returns. All seven templates under prompts/ still render with no leftover placeholder with {{execution_history_schema}} present in the context.

🤖 Generated with Claude Code

jerodw and others added 2 commits August 7, 2026 00:54
…vents.log

Implemented by the l5 harness story workflow.
actions/checkout defaults to fetch-depth: 1, so CI had a one-commit
repository. tests/test_story_011_validation.py resolves its pre-story
baseline by walking git log for orchestration/story_coordinator.py and
taking the newest revision whose blob lacks "execution-history"; with a
single commit there is no such revision and the guard raises, taking
down 28 tests. Reproduced with a --depth 1 clone (3 failed, 396 passed,
25 errors, matching CI) and confirmed fixed with a full clone (424
passed).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jerodw
jerodw merged commit 5b90a0e into main Aug 7, 2026
3 checks passed
@jerodw
jerodw deleted the story/story-011 branch August 7, 2026 15:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant