Repository navigation
Conversation
Birfy
left a comment
There was a problem hiding this comment.
Verdict: CHANGES REQUESTED
Tip SHA: 900f69a
Ran (on a trial merge of this tip into main 1aaf1bd):
python3 .github/scripts/risk_gate.py --repo ThinkFlowLab/agentdescent --pr 925→ T3 (pathagentdescent/runstore.py)pytest -q tests/test_run_events.py tests/test_run_panel.py→ 18 passedpytest -q(full suite) → no failures through ~81% before my 600 s local timeout; CItest (3.9–3.13)still running at review time
Not run: a browserEventSourceclient; the full suite to completion locally.
Blocking
- Unrelated
CNAMEfile at the repo root (agentdescent.hugoxk.online). It isn't part of this feature and isn't in the commit900f69a; it comes in from the fork's history (theMerge branch 'Birfy:main' into mainmerge on the fork'smain). Merged into this repo, a rootCNAMEcan repoint the project's GitHub Pages to a personal domain. Please drop it from the PR. The cleanest way is to branch from currentThinkFlowLab/agentdescentmain(not the fork'smain) and cherry-pick900f69a. - PR body still says
Tip SHA: PENDINGandpytest -q → PENDING. Please fill in the real tip and full-suite result once (1) is fixed.
Non-blocking (correctness/design)
- A stalled run streams forever. If the run process dies without writing a terminal status,
statestaysrunningand the handler thread loops until the client disconnects. A cheap guard would be to emit a terminalstatus(or close) whenstatus.json'spidis no longer alive, the same waystatus/list_runsdetect a stale run, or to cap the stream with a max idle time so the client reconnects. - Each poll re-parses the whole
rounds.jsonl. That's fine for tens of rounds, but tracking the byte offset (or at least only parsing lines past the last-seen count) would keep long runs O(new lines) per tick. - Status change and the rounds that caused it are emitted in status-then-rounds order. On the terminal tick, the final rounds are emitted after the status was read but before return, which is right. On a non-terminal state change, though, a client sees
statusbefore the rounds that preceded it. Consider reading rounds first, then status, so event order matches what happened. Last-Event-IDwith a negative or garbage value silently falls back to replay-all. That's reasonable, but a one-line test would pin it.- Keep-alive interval (15 s) is hard-coded while the poll interval is configurable; making it a module constant next to
EVENT_POLL_SECONDSwould let tests cover the keep-alive branch, which no test exercises today. _eventsduplicates the run-id traversal check indo_GET; hoisting it into one helper would keep the two endpoints from drifting.
Good
The read-only, loopback-only CORS boundary matches the JSON endpoints. X-Accel-Buffering: no, retry: and the round-index ids are the right SSE details, and the tests drive a real server on an ephemeral port with a small event_poll. The docs change in docs/plugins.md merges cleanly onto main as a small paragraph, so it's fine as is.
Risk tier T3: this PR needs Danyang's explicit merge click even after a PASS; it won't be auto-merged.
CNAME (agentdescent.hugoxk.online) is the fork's Pages file, not part of this feature; it entered the branch from the fork's main history. A root CNAME in the project repository would repoint its GitHub Pages to a personal domain.
The keep-alive interval was a literal while the poll interval was configurable; make it EVENT_KEEPALIVE_SECONDS so a test can exercise the branch. Both routes that take a run id now use one _run_id_in helper, so the SSE route and the JSON route cannot drift apart. Tests: a stalled run closes on its terminal status, a garbage Last-Event-ID replays every round, and a quiet stream sends keep-alive comments.
|
Please re-review. |
Birfy
left a comment
There was a problem hiding this comment.
Verdict: PASS
Tip SHA: bf4b25b
Ran (on a trial merge of this tip into main 1aaf1bd):
python3 .github/scripts/risk_gate.py --repo ThinkFlowLab/agentdescent --pr 925→ T3 (pathagentdescent/runstore.py)git diff --stat main <trial merge>→ only the 4 intended files (runstore.py,changelog.d/922-runstore-sse.md,docs/plugins.md+5/-1,tests/test_run_events.py); rootCNAMEis gonepytest -q tests/test_run_events.py tests/test_run_panel.py tests/test_runstore_cli.py→ all passed- CI
test (3.9–3.13),build, risk/reviewed-tip report checks → SUCCESS at this tip
Not run: a browserEventSourceclient; the full suite locally (CI owns it).
Both blocking items from the 900f69a review are fixed: the fork's CNAME is dropped and the PR body carries the real tip and suite result. The stalled-run case is now pinned by a test (dead pid → terminal failed status closes the stream), and keep-alive, garbage Last-Event-ID, and a shared _run_id_in guard are covered.
Non-blocking: each poll still re-parses all of rounds.jsonl, and status is emitted before the rounds of the same tick on a non-terminal change; fine for now, worth a follow-up if long runs get panels.
T3 (risk:T3): needs Danyang's merge click; not merging from the agent.
Fixes #922
What changed
Adds
GET /api/runs/<run_id>/eventsto the existing read-only run-storeserver. It is a Server-Sent Events stream:
statusevent fromstatus.json;roundevent per newrounds.jsonlline, with the round index as theevent id, so a reconnect can send
Last-Event-IDand resume after the lastround it saw;
statusevent when the run reachesdone,failedorcancelled,then the stream closes;
retry: 1000and periodic: keep-alivecomments;endpoints, and one
_run_id_inhelper shared by both routes for the run-idtraversal check.
The stream polls
rounds.jsonl(default everyEVENT_POLL_SECONDS = 0.5) anddoes not add a write path, an auth change or a background thread to the run
itself.
serve_http(..., event_poll=...)is a wall-clock knob, used by thetests to keep them fast. The keep-alive interval is
EVENT_KEEPALIVE_SECONDS.The root
CNAMEthat came in from the fork'smainhistory is removed(
0d21fef): it is not part of this feature, and a rootCNAMEwould repointthe project's GitHub Pages domain.
Verification at the tip
Tip SHA: bf4b25b
Ran:
pytest -q tests/test_run_events.py tests/test_run_panel.py→ 21 passedpython -m tools.gen_api_docs --check→docs/api.md is up to datepython -m tools.changelog_fragments check→70 changelog fragment(s) OKpytest -q(full suite) on a trial merge of this tip intomain1aaf1bd0: 20 failed, 10 errors, 30 node ids; that set is the same one, linefor line, that
main1aaf1bd0fails on this machine (docker/sandbox andenvironment failures in
test_sandbox_container,test_genesis_example,test_era_srbench,test_apply_noop,test_skill_library_*). This PR addsnone of them.
Not run:
EventSourceclient — the offline tests read the stream withurllib; the protocol is covered at the transport level.mkdocs build --strictto completion on this machine: no route tofonts.google.com, which the Materialsocialplugin fetches while buildingindex.md. Withsocialdisabled the docs build completes cleanly(
exit 0), and CI'sbuildjob runs the real one.Non-blocking items from the review
In the reviewer's order:
guard already exists:
RunDir.status()rewrites arunningrun whose pid isgone as
failed, and the stream closes on that terminal state (probed atthis tip: closes immediately with
state: failed). A test now pins it.Ending the stream for a live but wedged run needs a max-idle policy, which
is separate work with its own design question.
rounds.jsonl— deferred. Tracking a byteoffset changes the read model (torn lines, file replacement) and deserves
its own PR and tests rather than riding this review.
deferred with (2): changing the order changes what a reconnecting client
sees, and is easiest to judge together with the incremental reader.
Last-Event-IDtest for a negative or garbage value — fixed. Agarbage value replays every round; pinned by a test.
EVENT_KEEPALIVE_SECONDS, with a test that exercises the keep-alive comment._eventsduplicates the run-id traversal check — fixed. One_run_id_inhelper is used by both routes.