Skip to content

feat(0.29.0): AnalystRegistry.runStream — async event stream#59

Merged
drewstone merged 2 commits into
mainfrom
feat/analyst-registry-stream
May 19, 2026
Merged

feat(0.29.0): AnalystRegistry.runStream — async event stream#59
drewstone merged 2 commits into
mainfrom
feat/analyst-registry-stream

Conversation

@drewstone
Copy link
Copy Markdown
Contributor

Summary

  • AnalystRegistry.runStream(...) — async iterable that yields AnalystRunEvent discriminated-union events (run-started, analyst-skipped, analyst-started, analyst-completed, run-completed) in real time as analysts execute. Subscribers iterate with for await (const ev of registry.runStream(...)).
  • AnalystRegistry.run(...) becomes a thin collector over the same stream so the two surfaces share invariants — there is no second code path to drift.
  • Per-analyst events emit on the same boundary as the existing per_analyst summary, so a failing analyst surfaces as analyst-completed with summary.status === 'failed' (sibling isolation preserved).
  • New tests in analyst.test.ts cover: clean run event ordering, missing-input skip, throwing analyst → failed-status event, run()/runStream() envelope equivalence, and backpressure ordering.
  • Bumps to 0.29.0 (npm + pypi + python __version__); openapi spec rebuilds with the new version.

Test plan

  • pnpm test — 1158/1158 pass on 128 files
  • pnpm typecheck
  • pnpm build rebuilds dist/openapi.json with version 0.29.0

drewstone added 2 commits May 19, 2026 17:01
The registry now exposes a streaming counterpart to `run()`. UIs that
want to render progress (the 4-kind suite with maxDepth:3 takes minutes)
subscribe via `for await (const ev of registry.runStream(...))`;
consumers that just want the final envelope continue to await `run()`.

`run()` is a thin collector over `runStream()` — both share the same
loop body so they cannot drift on the invariants (isolation, hook
ordering, summary shape).

Event shape (`AnalystRunEvent` discriminated union):
  - run-started       { run_id, correlation_id, started_at, analyst_ids }
  - analyst-skipped   { summary }                       — missing input
  - analyst-started   { analyst_id, started_at }
  - analyst-completed { summary, findings }             — status: 'ok' | 'failed'
  - run-completed     { result: AnalystRunResult }      — terminal

Per-finding events deliberately omitted — analyzers are batch
operations (an Ax actor returns the full `findings:json[]` at the end
of the responder), so streaming inside one analyst would only emit
partial JSON consumers can't render. The kind-completion event is the
right granularity.

Tests (5 new, 53 total in analyst suite):
  - run-started → started → completed → run-completed for clean run
  - analyst-skipped when input is missing
  - analyst-completed with status=failed when analyst throws; siblings still run
  - run() returns the same envelope as the run-completed event from runStream()
  - back-pressure: slow consumer between events preserves ordering

`AnalystRunEvent` exported from index.ts. Hooks unchanged.
@drewstone drewstone merged commit 9f1e1f6 into main May 19, 2026
1 check passed
drewstone added a commit that referenced this pull request May 19, 2026
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