Repository navigation
Add internal run progress observer hooks to the engine - #30
Merged
Merged
Conversation
Introduce a private RunObserver that receives small scalar notifications as a run proceeds: plan, source start/finish, accepted and rejected records, trial start and finish, cancellation, and sink finishing. This is the foundation for live CLI progress; nothing user-visible changes yet. Observers are deliberately not result sinks: calls are synchronous with no added await points, carry no case payloads or exception text, and a raising observer is dropped without affecting the run's outcome or recorded evidence. The public arun() signature is unchanged and delegates to a private execute(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 23, 2026
Merged
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.
Executive summary
Adds a private
RunObserverhook to the execution engine. As a run proceeds, it receives small scalar notifications: the run plan, source start and finish, accepted and rejected records, trial start and finish, cancellation, and sink finishing. This is the first PR in the live CLI progress work (design option D: one line per case in scrollback plus a sticky status footer). Users see no change in this PR. The public API, CLI output, and artifact formats are all untouched.Why
Today
mic runprints nothing until the run finishes, so a three-minute eval looks like it has hung. Streaming progress needs signals the engine doesn't currently expose:Design and implementation
src/mic/_runtime/progress.pydefines:RunObserver, a protocol with nine synchronous methods;RunPlan,TaskPlan,SourcePlan,TrialError;Progress, a guard that forwards each call and drops the observer after its first exception.Why this isn't a
ResultSink:run.jsonreceipts.mic-event-v1.A progress display needs none of that, and must never be able to fail a run.
Engine (
engine.py):arun()now delegates to a privateexecute(..., observer=None); its signature is unchanged.trial_startedin the worker andfinishingbeforedelivery.finish.trial_finishedfires before the awaiteddelivery.write, so a cancelled write can't hide a trial that was already counted.interrupt()helper replaces the three places that setinterrupted, and firescancellingonce.Delivery (
delivery.py): a new read-onlyDelivery.unfinishedproperty lists sinks that are open but haven't settled. This lets the display name the sinks it's waiting on, such asbraintrust.What observers receive. Only scores, statuses, latencies, and the phase, exception type and scorer name for errors (
TrialError). Exception messages never reach an observer, which matches howfailure()already records errors. Labels come from the case's optionallabel.Verification and confidence
These are the risks this PR could introduce, and how each was tested (
tests/runtime/test_progress_observer.py):plannedcomes first andfinishinglast; each trial is accepted, then started, then finished, exactly once; the source finishes after its last accepted case; scores and the source summary match the result.cancellingfires exactly once; both running trials finish ascancelled; the two queued trials finish ascancelledwithout having started; the row that was never admitted gets no events; no trial starts aftercancelling.TrialError(phase, type, scorer), and "private" appears nowhere in the recorded calls.SourcePlanlists both, and each one's trials start the right number of times.finishinglists("jsonl", "capture")when recording, and excludes a sink that failed to open.uv run python scripts/verify.pyran 756 tests with 0 failures. Strict typing, authoring typing, lint, format and the coverage gates all pass.progress.pyhas 100% coverage, and every new engine line is covered (the remaining uncovered engine lines are older branches).Compatibility and risks
mic.__all__and thearun/runsignatures are unchanged.executeandRunObserverlive under the private_runtimepackage.Nonecheck when it isn't. No await points were added, so scheduling and cancellation behavior are unchanged.