story-011: Add execution-history.json written from the same path as events.log - #10
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A run's history existed only as
events.log, a human-readable stream.state.jsonsays 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 addsexecution-history.jsonbesideevents.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 theevents.logline 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 whatl5-statusreads and what the appendix documents, so freezing it is a requirement rather than a preference.History is evidence, never state. Nothing reads
execution-history.jsonto route;load_historyis called only byappend_event, for the next sequence number.Changes
orchestration/story_coordinator.py—append_eventgains keyword-only structured fields and the second output;load_historyis the read side.run_storycapturesstage_started_at = time.monotonic()where the stage-started event is already appended and reads it through a localelapsed()at every event that ends the stage, so a completed stage carries a duration the log only made derivable._escalatetakes**event_fieldsand 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,messageare required;stage,artifacts,duration_seconds,verifier_outcome,retry_decision,retry_reasonappear only on the events that have them. Written inside the subsetschema_validatorsupports (type,required,properties,items,enum) — optional fields are expressed by absence fromrequired, 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 frozenevents.logformat and unchangedl5-statusrender, 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 addsexecution-historyto its shipped-schema inventory. Both still assert exact set equality overschemas/; 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 whyexecution-history.jsonis deliberately not archived underattempts/attempt-N/(it is not a stage output, so it stays one continuous stream across every attempt).An entry's
artifactscome offstage.get("outputs", [])in the loaded workflow definition, never a list written into orchestration code — the same rule that already governs blocked paths, ownership prefixes, andarchivable_artifacts.Testing
424 passedon.venv/bin/python -m pytest tests/ -q, run from a clean tree at the head of this branch.Notes for review
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 pointHEADis 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 errorsagainst419 passeduncommitted. The retry replaced the baseline withpre_story_coordinator_source(), which walksgit log --format=%H -- <path>for the newest revision whose blob lacksexecution-historyand 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.mdrecords 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).changed-files.jsonlist no path undertests/at all; it lists three.tests/test_story_011_validation.pyis 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 ismay_not_create: ["tests/"], andcreatedholds 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 inverification-result.jsonunderunverifiedrather than waved through.l5-statuswas verified throughrun_status.format_detail/format_listingand thescripts/l5-statusentry point in tests, not by hand in a terminal.run_status.pyis unmodified and reads onlystate.jsonandevents.log, neither of which changed shape.context_assembler.schema_contextreturns. All seven templates underprompts/still render with no leftover placeholder with{{execution_history_schema}}present in the context.🤖 Generated with Claude Code