Repository navigation
fix(watcher): retire answered inbox pointers in the archive step; a restart sweep never blocks new tasks - #5162
qingyun-wu wants to merge 14 commits into
Conversation
… blocking new tasks A pool worker's inbox kept every 0-byte delivery pointer forever, and each watcher (re)start ran a resolver subprocess per pointer before reading any new event: 3,469 pointers took minutes, and owner messages sat unemitted behind it. - Retire: the step that archives a task body (task_archive.archive_file for the chat bridges, the gateway's _archive_result) now also moves that task's pointers into deliveries/<r>/archive/, under the folder's .lock. One owner for "this task is done". `task_archive.py retire-archived-pointers <ws>` is the idempotent migration for pointers whose task was archived earlier; a pointer whose body is still live in tasks/ is never moved. - Fast sweep: task_dispatch.py sweep-plan resolves the whole inbox with ONE `resolve-inbox-entry --batch` run and drops stale (typed no-payload past the race window) and answered entries in that one process. A resolver without --batch, or a plan without its done line, falls back to every entry. - Non-blocking: the sweep feeds the same FIFO fswatch writes, so a task that arrives mid-sweep is read in arrival order; dispatch_task's identity dedupe admits a file seen by both paths once. A standby now subscribes before it sweeps. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s env Run from a pool worker's shell, tests that built the watcher env from os.environ inherited SUTANDO_WORKSPACE_DIR / RESULTS_DIR / INSTANCE_ID and pointed the watcher under test at the live workspace, where it stamped and then removed the seat's own readiness sentinel. Python tests now use the existing clean_watcher_env.clean_env(); shell tests source the new tests/fixtures/clean-watcher-env.sh, which drops the same names. A pin test fails any new test that launches the real watcher without one of them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ve; the sweep never deafens Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The watcher now subscribes before it sweeps. Shell suites that run the real watcher passed on ubuntu only because a standby swept its inbox before discovering fswatch was missing, then exited 1. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
marklysze
left a comment
There was a problem hiding this comment.
Approve at 3e341803 (base main, which the head contains). I did not merge. The approval does not cover CI: the Python and shell lanes for this head were still running when I posted, and the shell lanes now install fswatch for the first time.
What I verified (I ran the code, not only the diff)
- The new tests pass, and they pin what matters.
delivery-pointer-retirementandtask-dispatch-sweep-planpass. I mutated the new code against them (14 mutants) and every one failed a test, including the two safety-critical ones: the migration retiring a pointer whose body is still live or not archived, and a refusal result dropped as "answered" (which would lose work). Also caught: retiring on non-task archives, following a symlink, skipping.claimed, noflock, a task id with a slash, ignoringtasks/processedor month folders, a fresh no-payload entry dropped inside the race window, and a broken batch dropping everything instead of keeping everything. - The sweep plan only drops what
dispatch_taskwould drop anyway. I compared the rules with the shell: stale means resolver rc 3 with an empty answer past the race window (the shell'sresolve_inbox_entrymaps that to rc 4 anddispatch_taskdrops it on the same age test); answered means a live, READY result whose first line is not the terminal refusal (samefind-readyhelper and the same anchored first-line test ashandler_result_is_answer). Everything else, including every other resolver rc, a missing batch header, a timeout or a crash, is kept fordispatch_taskto decide, so a plan failure degrades to the old per-entry sweep. - The archive step is careful: no-follow opens, regular-file checks,
fcntl.flockwith a 2 s bound that fails soft (pointer stays, the migration retries),archive/created under the lock, folders listed without following symlinks.src/task_archive.pyand the vendored copy are byte-identical andag2-sparrow-driftpasses. - Order in the watcher is right: the reader (
exec 3<) opens inlaunch_fswatchbefore the sweep subshell opens the FIFO for writing, so the open cannot block.
CI at this head: an earlier run of this same head failed four Python test files (agy-task-notifier, agy-notifier-dependency-gate, claude-task-notifier-submit, tmux-session-targets-are-exact). I ran all four at main (54d6465f) and at this head: they fail at both, and the PR touches none of the code they exercise. One correction to "identically": claude-task-notifier-submit fails 9-ish tests whose exact set varies from run to run on both trees (tmux/timing), so it is flaky, not a fixed list. I also hit watch-tasks-stream-bare-directory-event ("a second EOF past the relaunch budget ends the watcher") failing intermittently here, on main as well as on this head. In failing runs the process list shows the relaunched stub tail still alive after the test's second pkill, which looks like a race in the harness (the stub has not finished execing when pkill runs), not the watcher.
Questions and notes, none blocking
- FIFO atomicity is asserted, not shown. The sweep subshell and
fswatchnow write into one FIFO ("oneprintfper line, so writes never split a line"). A write is atomic only up toPIPE_BUF(512 bytes on macOS), and I do not know howfswatchbatches its writes under a burst. I tried a stress harness (realfswatchplus a concurrent sweep writer, slow reader); it did not complete, so I can neither confirm nor refute this. The impact is bounded (a mangled sweep line loses that one entry until the next restart), but a test that interleaves the two writers would settle it. - The migration is not wired in or documented.
retire-archived-pointersis a CLI only: nothing insrc/orskills/calls it and no doc names it. Existing inboxes still sweep fast through the plan, but the backlog of already-answered pointers stays until someone runs it by hand. Say how it is meant to run, or call it from the upgrade path. - The "is this an answer" rule now has two implementations (Python
_is_answer, shellhandler_result_is_answer), sharingfind-readybut not a parity test; I did not run both against the same inputs. Low risk, because the plan only drops entries and keeps anything uncertain, but a divergence would be silent. - Not reproduced: the live proof (3,469 pointers, new task emitted at +0.3 s).
…gration once; pin the answered rule (stacked on #5162) (#5170) * fix(watcher): relay fswatch through a line-atomic drain; run the pointer migration once; pin the answered rule Follow-up to #5162 (review asks from marklysze): - fswatch now writes to src/line_relay.py, which drains it at once and forwards each line in its own write() to the event FIFO. Measured on macOS: fswatch 1.18.3 segfaults when a burst (~1,000+ events) backs up behind a full pipe; through the relay it survives, and no line is ever split or merged beside the sweep's writes. src/inbox-events.sh `start_inbox_events` is the launcher. - The watcher runs `task_archive.py retire-archived-pointers` once per workspace, before its first sweep plan, under each inbox's .lock, and records the run in state/migrations/retire-archived-pointers.v1.done. Documented in docs/worker-pool-design.md. - tests/answered-rule-parity.test.py runs the shell and Python "answered" rules over one table. It found that the shell rule read a refusal with no trailing newline as an answer (`read || first=""` cleared the line); fixed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(watcher): the broken-helper stub still lets the event relay stream The stub interpreter that proves the sweep's mtime fallback now also stood in for src/line_relay.py, so no event reached the watcher on Linux. It passes the relay through to the real interpreter, as readiness-window-decision-instant does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings in the #4303 revert (#5166), the CI sharding change (#5000) and the rest of main since 54d6465. One conflict, in the CLI usage string of src/delivery/task_dispatch.py: this branch adds the `sweep-plan` line above `pending-candidates`/`next-pending`, while main's revert drops `[--typable]` from those two lines. Resolved by keeping the new line and taking main's text for the two existing ones. No other change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Merged What conflictedOne file, one hunk:
Resolved by hunk: the new Everything else merged cleanly ( Tests (merge head, all
|
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 93%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changes
Summary
src/delivery/task_dispatch.pyLines 619-627 619 f"{counts['stale']} stale, {counts['answered']} answered, {len(keep)} to dispatch "
620 f"(batch resolve: {counts['batch']}; {int((time.monotonic() - started) * 1000)} ms)",
621 file=sys.stderr, flush=True)
622 if counts["batch"] == "unavailable":
! 623 return 1
624 base = str(Path(inbox))
625 for name in keep:
626 print(f"{base}/{name}")
627 print(SWEEP_PLAN_DONE) |
|
CI on The failing check is the required Every missing range is code the suites reach only as a child process of the watcher's bash ( |
marklysze
left a comment
There was a problem hiding this comment.
Approve at bbde097b (full bbde097b8c5ddad0fca540324423f2359bbcdc76, base main), on the code. I did not merge. The required diff coverage >= 95% (python) check is failing on this head (68%, 88 of 280 changed lines), so this cannot merge as it stands; the author's comment names the cause and the fix, and I did not run coverage myself.
What I re-read. The ask says this head is origin/main merged in with one usage-string conflict and an unchanged change set. The merge part is right, but the head is not only a merge: since my approval at 3e341803 it also contains #5170 (553ef5e3), the follow-up to my review asks, so I reviewed that as new code.
- The merge:
mainis contained in the head, and the only conflict is_USAGEinsrc/delivery/task_dispatch.py.main's revert of #4303 had dropped[--typable]frompending-candidatesandnext-pending; the resolution keeps the newsweep-planline and takesmain's text for the other two.git diff --numstat origin/main HEAD -- src/delivery/task_dispatch.pyis145 0(purely additive), and--typableoccurs 0 times in the head and inmain. - #5170 (+411/-10, 11 files):
src/line_relay.py(new),src/inbox-events.sh(new launcher), the watcher's one-timeretire-archived-pointersrun, and a fix to the shell "answered" rule.
Verified
- Tests pass at the head:
answered-rule-parity,inbox-events-burst,watch-tasks-stream-pointer-migration,delivery-pointer-retirement,task-dispatch-sweep-plan,watch-tasks-stream-priority-sweepandwatch-tasks-stream-sweep-does-not-block-new-tasks.watch-tasks-stream-readiness-window-decision-instantfails on this host, identically onorigin/main, on3e341803and on the head (twice each), so it is not caused by this PR (the author reports two pre-existing misses). - The migration runs off the event loop's path:
retire_archived_pointers_onceis called fromsweep_entries, whichstartup_sweepruns in a background subshell, so a slow migration cannot block a new task.$!still names fswatch, not the relay, under macOS/bin/bash3.2.57 (I tested the samecmd > >(proc) &shape). - Mutation, 9 decisions, 6 caught: fswatch writing straight to the FIFO (no relay), the relay not draining at once, the shell
readclearing an unterminated line again, the migration marker never written, not honoured, or the migration not run before the first plan.
Not blocking: three survivors (untested claims)
- Per-line writes. Making the relay coalesce several lines into one
write()passes, so "each line leaves in its own write()", which is what keeps a line whole beside the sweep's writes, is not pinned. - An unterminated last line being dropped by the relay passes.
- A failed migration being recorded as done passes (the code retries on the next start; nothing pins that).
Not blocking, as design notes. The relay's in-memory queue is unbounded, so a reader that stalls grows it; and a line longer than PIPE_BUF (512 on macOS) is no longer atomic, which the watcher's inbox paths stay well under.
The failing gate. The comment lists line_relay.py at 0%, task_dispatch.py at 62.7% (sweep-plan), resolve_inbox_entry.py at 72.7% (--batch) and task_archive.py at 91.6%, and says the suites reach those only as child processes of the watcher, which coverage.py cannot see. That is consistent with what I ran (the tests pass and the lines are uncovered); the fix is direct unit tests, which the author says is next.
Not verified: the coverage run itself, CI beyond the check list (31 success, 2 skipped, 1 failure), and a live watcher restart under a real burst on a seat with thousands of pointers.
… paths in-process The diff-coverage gate reads coverage.py data from the test process only, and the suites reached line_relay, `task_dispatch sweep-plan`, `resolve_inbox_entry --batch` and the `task_archive.retire_*` OSError branches solely as children of the watcher's bash (fswatch | relay, startup_sweep), so the gate saw 0% on the relay and 62-72% on the rest. One standalone test imports the modules and drives the same paths directly: the relay's drain-before-write, EOF and broken-pipe exits; `_sweep_plan_cli` through `_main` with argv lists (usage errors, empty inbox, counts line, the pool resolver); `_resolve_batch` and `_is_answer` failure branches; `resolve_inbox_entry.batch` / `main` for hit, no-payload, undelivered and empty; and the archive step's unlistable root, non-regular lock and missing archive/deliveries directories. Test-only; no production change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Coverage follow-up to the main merge ( Before — the gate's verdict on After — the gate's own method locally ( The three remaining misses are in What the new file covers: Test run — the new file plus every test file this PR touches, clean env, macOS: 33 PR-touched files: 30 pass on first run. CI on |
…failed pointer migration Builds on c646419 (tests/watcher-sweep-paths-in-process.test.py), which already lifted the four modules to 100% on changed lines in-process. This adds the behaviours a mutation run found no test for: - line_relay: lines read in one batch still leave in one write() each (a coalescing mutant passed the byte-equality check), an unterminated last line is terminated as its own write, and a short write resumes from where it stopped (the drop-the-loop mutant passed). - watch-tasks-stream: a migration that fails writes no marker and is run again on the next start, while the sweep still plans the inbox (SUTANDO_PY pointed at a python that fails only that subcommand). Diff coverage, the gate's own method (coverage run --rcfile=.coveragerc per file, combine, xml, diff-cover --compare-branch=origin/main --fail-under=95) over the PR-touched Python suites: before (gate verdict on bbde097): Total 280 lines, Missing 88, 68% after (this head): Total 280 lines, Missing 3, 98% resolve_inbox_entry.py 100%, task_dispatch.py 100%, line_relay.py 100%, task_archive.py 100%; remote_gateway_bridge.py 296,3703-3704 are covered by suites outside this subset (100% on the full CI run). Mutants A (coalesce), B (drop unterminated), C (single write) each fail at least one of the new cases and pass without them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
marklysze
left a comment
There was a problem hiding this comment.
Approve at fc6e5623 (full fc6e5623c2c796335a94d994f9d9f86ccec2e4a2, base main), replacing my approval at bbde097b. I did not merge. The required diff coverage >= 95% (python) check had not reported on this head when I looked (CI was still running), so that remains the thing to watch; my own approximation below is not the gate.
What changed since bbde097b: two commits, test-only. git diff --stat bbde097b fc6e5623 -- . ':(exclude)tests' is empty, so no source file differs from the head I approved. The diff is tests/watcher-sweep-paths-in-process.test.py (new, +422) and a +18/-2 change to tests/watch-tasks-stream-pointer-migration.test.py.
Verified
- The new tests pass:
watcher-sweep-paths-in-process.test.py(37 tests, OK) andwatch-tasks-stream-pointer-migration.test.py. - The three survivors from my last review are now caught, and so are two more: the relay coalescing several lines into one
write(), an unterminated last line being dropped, a failed migration being recorded as done, a short write not being resumed, and the relay'sBrokenPipeErrorhandling removed (5 of 5 caught, with the three-file setwatcher-sweep-paths-in-process,pointer-migrationandinbox-events-burst). - My approximation of the gate: I ran
coverageover the five test files I could run here (the new in-process test,delivery-pointer-retirement,task-dispatch-sweep-plan,pointer-migration,answered-rule-parity) anddiff-coveragainstorigin/main:resolve_inbox_entry.py,task_dispatch.pyandtask_archive.pyat 100%,line_relay.pyat 79.4% (lines 24-30, the drain thread), 7 of 277 lines missing, 97% overall. That is above the bar but it is a subset of CI's suites and my tooling, so the gate's own number is what counts; the author reports 98%. - CI so far at this head: the shell tail shards,
shellcheck,ruffand the repo's refusal checks are success,shell tests (lanes)was still running.
Not verified: the gate's own result on this head (pending when I looked), and the unchanged items from my last review (a live watcher restart under a real burst; the readiness-window test that fails on this host identically on main).
|
CI result. The first run on (68% → 97%. The seven lines are the relay's drain-thread body, which the runner's coverage did not attribute to the thread; the same tests report 100% on it locally.) |
liususan091219
left a comment
There was a problem hiding this comment.
Ready to approve.
Reviewed head fc6e5623. marklysze approved at this head with a substantive review that includes mutation checks.
What I ran (fresh worktree, env -i):
watch-tasks-stream-sweep-does-not-block-new-tasksPASS,delivery-pointer-retirement13 OK,task-dispatch-sweep-plan12 OK,watcher-sweep-paths-in-process37 OK,watch-tasks-stream-pointer-migrationPASS,answered-rule-parityPASS,watcher-tests-never-inherit-a-seat-envPASS,inbox-events-burstPASS,task-dispatch-contract104 OK,watch-tasks-stream-priority-sweepPASS.- Failure mode 1: I put
watch-tasks-stream.sh,task_dispatch.pyandresolve_inbox_entry.pyback to the merge-base (95fbd566). The non-blocking test fails 4 checks: the new task is emitted only after 20.03s, and stale pointers each cost a per-entry resolver run. With the files restored, it passes. - Failure mode 2: I stubbed out the
retire_delivery_pointerscall intask_archive.archive_file.test_archive_file_retires_the_pointer_in_the_same_stepfails. So do the vendored-copy parity test and its pin. - Sibling check: the discord, slack and telegram bridges all archive through the shared
task_archive.archive_file, and the gateway goes through_archive_result. Both now retire. The voicearchiveFileintask-bridge.tsis not covered, and the PR body names that. scripts/review-checks.sh --diff: PASS.
Gates: CI is green (33 success, including diff coverage), license/cla is success, the PR is mergeable and has no unresolved threads.
Still owed (named, not blocking): a live watcher restart on a real seat with a real inbox. The author's evidence is a real watcher, resolver and fswatch run over a copy of a 3,469-pointer inbox, not a live post-restart round trip.
Nits:
- The diff is +2,081 lines, mostly tests. The seat-env test isolation (the second commit) is a separate concern that could have shipped on its own.
- After merge, run
retire-archived-pointersonce per pooled host. Without it, the old pointers stay at the top level (each is now cheap in the batch plan).
— Sutando (susans-MacBook-Pro)
liususan091219
left a comment
There was a problem hiding this comment.
Commenting at fc6e5623c. This is not a change request. It withdraws my "Ready to approve." of 2026-10-09: that review said the PR was mergeable, and it no longer is.
Why I'm not approving this head
- It conflicts with main. It is 249 commits behind, and a dry merge conflicts in code, not just docs:
src/watch-tasks-stream.sh,src/delivery/task_dispatch.pyandpackages/ag2-sparrow/ag2_sparrow/remote_gateway_bridge.py, plusdocs/catalog.jsonand 8 watcher test files. - The watcher has changed a lot on main since this head, for example #5792 (priority in worker startup sweeps), #5801, #5803 and #5614. The sweep and subscribe ordering this PR moves sits right in that code. So resolving the conflicts is real review work, and an approval at
fc6e5623cwould not cover the head that merges. - The feature is not on main by another route:
retire_delivery_pointersandsweep-plando not appear there.
Unchanged from my last review
- The code at
fc6e5623cmet the bar: marklysze's substantive approval with mutation checks, and my own failure-mode runs. - Still owed (named, not blocking): a live watcher restart on a real seat with a real inbox.
What would make it approvable
- Rebase or merge main, with the watcher conflicts resolved.
- A fresh expert review of the conflict resolution at the new head. I will review that head too.
— Sutando (susans-MacBook-Pro)
liususan091219
left a comment
There was a problem hiding this comment.
Comment at fc6e5623c (head unchanged since my comment of 2026-10-24). Not a change request; not approvable yet.
Missing: the branch conflicts with main.
git merge-tree --write-tree origin/main fc6e5623cagainstmain02cb7e8c(435 commits ahead of this branch's merge base) still conflicts in code:src/watch-tasks-stream.sh,src/delivery/task_dispatch.py,packages/ag2-sparrow/ag2_sparrow/remote_gateway_bridge.py, plusdocs/catalog.json,docs/src-map.mdand 8 watcher test files.- Since my last comment,
mainchanged the watcher again: #6026 (a standby never takes a live session watcher's sentinel) and #6172 (a foreign-inbox watcher never takes a live watcher's sentinel). The resolution has to compose with those guards as well as with the sweep changes named last time. - Not superseded:
mainstill has noretire_archived_pointers/sweep_plan.
Unchanged
- No change-request condition fires. The source reads in the new tests are an extracted-and-executed shell function (
handler_result_is_answerparity) and a vendored-copy byte-equality pin, not source-greps. - marklysze's approval is at this head, but approvals are not dismissed on push here, so it would carry over a hand-resolved watcher merge nobody has reviewed. A fresh expert review of the resolved head is needed.
- Owed, named, not blocking: a live watcher restart on a real seat with a real inbox.
CI at this head: green; license/cla: success (1 context); 0 unresolved threads.
— Sutando (susans-MacBook-Pro)
Preserve durable gateway settling, catch-up sweeps and resolved worker priority. Keep failed archive pointers live, and adapt regression fixtures to asynchronous startup and platform pgrep selection. Sutando-Task: task-a9d60ad5a3ba0f40f6
Sutando-Task: task-a9d60ad5a3ba0f40f6
Sutando-Task: task-a9d60ad5a3ba0f40f6
liususan091219
left a comment
There was a problem hiding this comment.
Commenting at b5f72805. Not a change request, and not approvable yet.
The main merge I asked for on 2026-11-02 has landed (8e879153), but this head is not ready.
Missing:
-
CI fails on this PR's own new tests (run 41593786387):
hermetic-env-structural:tests/delivery-pointer-retirement.test.py:165: unisolated.watcher-fixtures-are-bound:tests/watch-tasks-stream-pointer-migration.test.py:56andtests/watch-tasks-stream-sweep-does-not-block-new-tasks.test.py:94start a watcher outsidepopen_boundorbind_to_test.
Both are repo guards that the new files trip. The same shard also failed
bench-claim-backends-smoke("correctness invariant violated"). Separately, the sparrow publish gate failedtest_keepawake_restarts_nothing_when_the_generation_rolls_at_the_final_show, which the body flags as outside this change. I have not established a main baseline for either. -
It conflicts with main again. After #6151 and #6131,
git merge-treeagainste156b66aconflicts only inCHANGELOG-PENDING.md, so this one is trivial. -
A fresh expert review of the resolved watcher merge. marklysze's approvals are at
fc6e5623and older. They don't cover the hand-resolved merge with #4594's catch-up, #6026 and #6172.
Unchanged:
- No change-request condition fires on the code I have read.
- No added line hardcodes a workspace path.
- Owed, named, not blocking: a live watcher restart on a real seat with a real inbox.
license/cla is success (1 context), and there are 0 unresolved threads.
— Sutando (susans-MacBook-Pro)
Preserve pool cleanup before asynchronous batch planning and isolate the incoming watcher regression fixture. Canonicalize the shutdown fixture path for macOS. Sutando-Task: task-798bb57692b4af7645 Co-Authored-By: Codex <noreply@openai.com>
Bind watcher fixtures to their test process and isolate the archive CLI environment. Sutando-Task: task-798bb57692b4af7645 Co-Authored-By: Codex <noreply@openai.com>
|
@cla-assistant check |
liususan091219
left a comment
There was a problem hiding this comment.
Commenting at aa1d0298. Not a change request; not approvable yet, but two of the three things I named at b5f72805 are now done.
Done since b5f72805
- CI is green at this head (48 success, 3 skipped, 0 failing): the
hermetic-env-structuralandwatcher-fixtures-are-boundguards that failed the PR's own new tests pass now. The fix isaa1d0298(+11/-6, tests only):tests/delivery-pointer-retirement.test.pyruns underisolate_os_environ()and gives its CLI subprocesshermetic_env();tests/watch-tasks-stream-pointer-migration.test.pyandtests/watch-tasks-stream-sweep-does-not-block-new-tasks.test.pystart the watcher throughpopen_boundinstead of a barePopen(..., start_new_session=True). No source file changes in that commit.diff coverage >= 95% (python)and all three sparrow publish-gate shards are success too. - No conflict with
main.b7ea1766mergesmain(f31d8190); GitHub reports the head MERGEABLE, 1 commit behind. Againstmainthe PR is +152/-0 insrc/delivery/task_dispatch.py, +56/-24 insrc/watch-tasks-stream.sh, +6/-2 inpackages/ag2-sparrow/ag2_sparrow/remote_gateway_bridge.py, 55 files in all.
Still missing: an expert review at the resolved head.
@marklysze's approval is at fc6e5623 (2026-10-07), before the two main merges (8e879153, b7ea1766) that hand-resolved src/watch-tasks-stream.sh, src/delivery/task_dispatch.py and the sparrow bridge against #4594's catch-up, #5792, #6026 and #6172. Approvals are not dismissed on push here, so that approval would carry over a watcher merge nobody has re-read. One substantive re-review at aa1d0298 (or later) meets the bar; the alternative route (three approvals from known people) is not in reach with one approver.
Unchanged
- No change-request condition fires on the delta. The
read_textsites in the tests remain an extracted-and-executed shell function (handler_result_is_answerparity), anast-extracted real_archive_task_filerun in-process, and a vendored-copy byte-equality pin, not source-greps. - No added line hardcodes a workspace path.
- Owed, named, not blocking: a live watcher restart on a real seat with a real inbox.
license/cla is success (1 context); 0 unresolved threads.
— Sutando (susans-MacBook-Pro)
A worker inbox retained its empty delivery pointers after task bodies were archived, so restarts repeatedly resolved old work and delayed new events. This PR retires pointers through the shared task archive owner, migrates previously archived pointers once, and feeds a batch startup sweep into the event FIFO after subscribing.
The watcher preserves gateway durable settling, periodic catch-up, and worker priority resolution. Successful batch plans order pointers by resolved payload priority; resolvers without batch support use bounded priority lookup and a resolution cache. A failed task archive leaves its pointer live for retry. A line relay lets bursts and sweep paths share the FIFO without interleaved batch writes. Retirement uses the inbox lock and does not follow symlinked inbox directories; migration leaves pending bodies untouched.
The merge from main retains pool-driven finished-delivery cleanup before migration and batch planning inside the asynchronous sweep. Parent-side dead-claim reconciliation remains intact. Review
src/watch-tasks-stream.sh, the shared task archive owner, anddocs/worker-pool-design.mdfirst. The documentation covers both archive-driven and pool-driven retirement.Validation of the merge and follow-up at
aa1d029867d8317e98c9dc2d1be09dfbc7e31bed:No live workspace migration or service restart was performed. CI on this head completed with 48 successful checks and three skipped, with no failed or pending checks. CI run.
Agent-assisted: Codex for qingyun-wu.
Sutando-Task: task-798bb57692b4af7645