Skip to content

Make a run say what it did without - #210

Merged
shanev merged 2 commits into
mainfrom
feat/degradations-abi-v10
Aug 4, 2026
Merged

Make a run say what it did without#210
shanev merged 2 commits into
mainfrom
feat/degradations-abi-v10

Conversation

@shanev

@shanev shanev commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Why

Several boundaries deliberately refuse to let a host-supplied callback end a run — a broken logging sink must not destroy a user's work. That resilience is correct. It was paid for with a warnings.warn that reaches no consumer of the result.

Demonstrated before this change — a sink that raises once, mid-stream:

sink invocations: 4
events actually delivered: 3
journal length (events that happened): 4
ledger believes emitted: 4
warnings: ['budget event sink failed: RuntimeError: transient …']
LOST EVENTS: 1

The event is gone, the ledger believes it was delivered, and every consumer sees a stream that is simply one event short. A run that lost an event, ran its terminal extract without host context, or discarded salvageable work returned something byte-identical to a clean run.

What

RunDegradationsite, error_type, detail, and consequence, the last recording not that a callback raised but what the run then did without:

site consequence
budget_event_sink a budget event was produced but never delivered
extract_context_provider terminal extract ran without host-held observations
extractable_work_probe run treated as having no salvageable work; extract skipped

Exposed on RLMResult.degradations and — via Trace ABI v9 → v10 — on the result event, so a host across the wire sees it too. BudgetLedger.dropped_events() exposes the same facts for a directly-held ledger.

Empty on a clean run and always present: absence must never be how a consumer learns nothing was lost. droste_runner emits degradations: [] on every response shape for the same reason.

Nothing new can end a run. The fallbacks are unchanged; only their silence is.

On the ABI bump

The nested result payload is a closed schema despite a comment claiming it "keeps its own schema authority" — adding the field is necessarily an ABI change. TS treats the nested result as opaque, so the relay needed only the envelope bump.

Verification

1268 passed, 3 skipped; 65 Deno tests. Includes an end-to-end test asserting a recorded degradation actually arrives on the emitted result event — recording something the host never receives would be the same defect one layer up.

🤖 Generated with Claude Code

shanev and others added 2 commits August 4, 2026 13:58
Several boundaries here deliberately refuse to let a host-supplied callback
end a run: a broken logging sink or observer must not destroy a user's work.
That resilience is correct. It was paid for with a warnings.warn that reaches
no consumer of the result, so a run that lost a budget event, ran its terminal
extract without host context, or discarded salvageable work returned something
byte-identical to a clean run. The loss was real and unobservable.

A demonstration, before this change: give BudgetLedger a sink that raises once
mid-stream and four reservations produce three delivered events, a journal of
four, an _emitted_events of four, and one RuntimeWarning. The event is gone,
the ledger believes it was delivered, and every consumer sees a stream that is
simply one event short.

Add RunDegradation -- site, error_type, detail, and consequence, the last
recording not that a callback raised but what the run then did without.
Recorded at budget_event_sink, extract_context_provider, and
extractable_work_probe; exposed on RLMResult.degradations and, via Trace ABI
v10, on the result event so a host across the wire sees it too. Empty on a
clean run and always present: absence must never be how a consumer learns
nothing was lost.

Nothing new can end a run. The fallbacks are unchanged; only their silence is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The done event reports the run's terminal shape, so a host that reads only
that event would have learned nothing about what the run did without. Add the
field there too, in both the Python schema and the relay's, and drive the CI
corpus checks off the enumeration rather than naming helpers -- naming them is
what left release.yml behind on v9 and this workflow behind on v10.

Also fixes fixtures I regenerated with Python's default ensure_ascii, which
escaped a literal em dash to — and changed bytes the conformance corpus
is compared against.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shanev
shanev merged commit 315f69c into main Aug 4, 2026
11 checks passed
@shanev
shanev deleted the feat/degradations-abi-v10 branch August 4, 2026 18:25
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