ADR-0052: Run history is a controller-supplied view of a launch series
Status: accepted | Date: 2026-09-28
References: RFC-0002:C-RUN-HISTORY, ADR-0027, ADR-0037
Context
A scheduled playbook that repairs something works better when it knows what its last few runs found and tried. The runs that timed out or could not fix anything are the ones the next agent most needs to see.
The one channel between runs today is the schedule cursor. It is a position, not a log. It advances only when a run finishes, so a failed or timed-out run leaves no trace. In its file form, the pack writes the file and the controller stages it into the next firing, which makes the pack carry and prune its own history. ADR-0027 kept dedupe state out of the engine, and RFC-0002:C-TASK-FILES forbids declared files as a channel between runs. The cursor is the one sanctioned exception, and it exists for input positions. The controller's ledger already holds every run of every standing launch (ADR-0037) with its outcome and session.
Decision
History is a read-only view the controller builds from its own ledger, and no run writes to it. What each run contributes is the declared output of one task the pack names as its record. RFC-0002:C-RUN-HISTORY states the record's shape, the ordering, the bounds, and how a task receives it.
The pack declares history, not the schedule. Whether a task reads its last few runs is part of what the pack does, so it belongs where the pack's tests can see it, and a manual launch simply gets nothing.
The series is the standing launch.
The controller does not compile the pack to learn how much history it needs. It sends up to the engine's ceiling with every launch in a series, and the engine trims to each task's depth.
Consequences
A failed, stopped, or timed-out run is part of the history. Retention belongs to the controller, so a pack never prunes anything.
What one run can tell the next is exactly what its record task declares. An earlier agent's text reaches the next agent's prompt only as marked external input.
A pack's record shape can change between revisions within one series. Each record carries the revision that produced it, and a reader has to tolerate older shapes.
Every launch in a series carries up to the ceiling's worth of records, even when no task reads history. A resumed run must see the history the interrupted run saw, so the supplied history is persisted with the run, which costs ledger storage per run.
Two overlapping runs of one series can see different histories, because a run still in flight is not part of the history.
The engine needs the history at start, so the launch contract gains one more supplied input across the controller/engine boundary RFC-0004 inventories.
Alternatives Considered
The pack declares a record task and per-task depth; the controller supplies up to the ceiling from its ledger and the engine trims (accepted)
- Pros: Includes every terminal run, failures and timeouts too, No state the run writes; retention and bounds stay with the controller, Visible and testable in the pack, The controller never compiles a pack to launch it
- Cons: One more input across the controller/engine boundary, Records travel with every series launch whether or not a task reads them
The controller compiles the pack and sends exactly the depth its tasks declare (rejected)
- Pros: Sends only what the run will read
- Cons: The controller compiles the pack before every launch, which today only the engine does, and needs the compiled plan's depths across the boundary
- Rejected because: At most 30 bounded records is cheaper than a second compilation path in the controller.
Extend the schedule cursor file to advance on every terminal outcome (rejected)
- Pros: Reuses the cursor table and staging
- Cons: A cursor that skips past an unfinished run is wrong for the input-processing packs cursors exist for, The pack writes and prunes its own log, which is a store between runs
- Rejected because: A cursor is a position and must not advance past unprocessed input; history is a log that must include failures.
The schedule declares the history depth (rejected)
- Pros: No DSL change
- Cons: The pack cannot tell whether it will receive history, and its tests cannot exercise it
- Rejected because: Reading history is the pack's behaviour, so the pack states it.
The pack queries the controller's run API for its predecessors (rejected)
- Pros: No new engine input
- Cons: Needs a controller credential inside the run, No projection: the pack could read prompts, stdout, and files of earlier runs
- Rejected because: It hands the run a credential and bypasses the declared-output projection that keeps what crosses runs bounded.