Skip to content

fix(watcher): retire answered inbox pointers in the archive step; a restart sweep never blocks new tasks - #5162

Open
qingyun-wu wants to merge 14 commits into
mainfrom
fix/watcher-retire-answered-pointers
Open

qingyun-wu wants to merge 14 commits into
mainfrom
fix/watcher-retire-answered-pointers

Conversation

@qingyun-wu

@qingyun-wu qingyun-wu commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

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, and docs/worker-pool-design.md first. The documentation covers both archive-driven and pool-driven retirement.

Validation of the merge and follow-up at aa1d029867d8317e98c9dc2d1be09dfbc7e31bed:

  • All 42 selected watcher, archive, dispatch, priority, and pool-delivery test files have passing results, including 15 pointer-retirement tests and 13 batch-sweep tests.
  • Main’s fixture guards also pass: four process-lifetime checks and 28 hermetic-environment checks. The three affected fixtures pass after adopting the shared cleanup and environment helpers.
  • The incoming finished-delivery regression passes all five assertions; the watcher fixture environment guard passes.
  • Standby sentinel ownership passed an isolated retry after a diagnostic timing failure in the concurrent run. Shutdown behavior passed after canonicalizing its fixture path to account for macOS temporary-directory aliases.
  • Shell syntax, ShellCheck, Ruff for changed production Python, review-checks, source-map consistency, whitespace checks, and packaged-source synchronization pass.
  • Independent second-pass review found no merge-induced blocker. All four conflicting files were resolved hunk by hunk.

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

qingyun-wu and others added 3 commits October 6, 2026 11:52
… 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>
qingyun-wu and others added 2 commits October 6, 2026 12:11
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>
@qingyun-wu qingyun-wu closed this Oct 6, 2026
@qingyun-wu qingyun-wu reopened this Oct 6, 2026

@marklysze marklysze left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-retirement and task-dispatch-sweep-plan pass. 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, no flock, a task id with a slash, ignoring tasks/processed or 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_task would drop anyway. I compared the rules with the shell: stale means resolver rc 3 with an empty answer past the race window (the shell's resolve_inbox_entry maps that to rc 4 and dispatch_task drops it on the same age test); answered means a live, READY result whose first line is not the terminal refusal (same find-ready helper and the same anchored first-line test as handler_result_is_answer). Everything else, including every other resolver rc, a missing batch header, a timeout or a crash, is kept for dispatch_task to decide, so a plan failure degrades to the old per-entry sweep.
  • The archive step is careful: no-follow opens, regular-file checks, fcntl.flock with 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.py and the vendored copy are byte-identical and ag2-sparrow-drift passes.
  • Order in the watcher is right: the reader (exec 3<) opens in launch_fswatch before 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 fswatch now write into one FIFO ("one printf per line, so writes never split a line"). A write is atomic only up to PIPE_BUF (512 bytes on macOS), and I do not know how fswatch batches its writes under a burst. I tried a stress harness (real fswatch plus 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-pointers is a CLI only: nothing in src/ or skills/ 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, shell handler_result_is_answer), sharing find-ready but 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).

qingyun-wu and others added 2 commits October 6, 2026 18:12
…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>
@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

Merged origin/main (95fbd566a) into this branch as a merge commit (bbde097b8), on top of 553ef5e3 — the squash of #5170 into this branch, which stays as-is. No rebase, so the PR's own commits and the approval context are unchanged.

What conflicted

One file, one hunk: src/delivery/task_dispatch.py, the _USAGE string.

Resolved by hunk: the new sweep-plan line is kept, and the two existing lines take main's text (no [--typable]). Nothing else in the file is touched by the merge; git diff origin/main HEAD -- src/delivery/task_dispatch.py is purely additive (146 added, 0 removed lines). The merged tree has no remaining --typable / TASK_NAME_RE references.

Everything else merged cleanly (.github/workflows/ci.yml alongside the #5000 sharding change included). The PR's change set is unchanged: the set of added/removed lines in git diff origin/main HEAD outside the conflicted file is identical to the pre-merge git diff 54d6465f 553ef5e3.

Tests (merge head, all SUTANDO_* / TMUX* / AGENT_ID / AG2_AGENT_NAME unset, env -i, temp HOME, suites run one at a time from the worktree root)

$ python3 run_suites.py <worktree> <logs> 'tests/watch-tasks-stream-*' 'tests/watcher-*.test.py' 'tests/watcher-*.test.sh' \
    tests/answered-rule-parity.test.py tests/claude-task-notifier-standby-log.test.sh tests/delivery-pointer-retirement.test.py \
    tests/inbox-events-burst.test.py tests/task-dispatch-sweep-plan.test.py tests/worker-mode-is-gated-on-env.test.py \
    tests/task-dispatch-contract.test.py tests/task-archive.test.py 'tests/delivery-*.test.py' 'tests/pool-*.test.py' 'tests/worker-*.test.py' \
    tests/check-pending-tasks-worker-pool.test.sh tests/core-never-names-the-worker-pool-skill.test.py 'tests/skills/worker-pool/*.test.py' \
    'tests/skills/worker-pool/*.test.sh' tests/sync-from-src-repo-relative.test.py tests/task-queue-counts-the-announcing-inbox.test.py
# each suite: env -i HOME=<tmp> TMPDIR=<tmp> PATH=... {python3|bash} <file>, one at a time, 941 s total

110 suites, 108 passed, 2 failed (first run):
  FAIL rc=1    0.4s tests/watch-tasks-stream-shutdown-emit.test.sh
  FAIL rc=1   21.8s tests/watch-tasks-stream-unready-holder-restamp.test.sh

Also:

$ python3 packages/ag2-sparrow/tools/sync_from_src.py --check
in sync with src/ ✓
$ python3 packages/ag2-sparrow/tools/test_no_drift.py
PASS — package in sync with src/
$ bash scripts/review-checks.sh --diff <(git diff origin/main HEAD)
review-checks: PASS (hardcoded-paths + root-artifacts + prose-cap clean)

The 3,000-pointer timing check is tests/watch-tasks-stream-sweep-does-not-block-new-tasks.test.py (3,000 stale pointers + 1 new task); its result is in the list above.

Approval was at 3e341803; #5170's squash and this merge both post-date it, so re-requesting @marklysze.

Both failures checked against the other heads in the same isolated env:

=== 553ef5e3 (this branch before the merge) ===
FAIL rc=1    0.4s tests/watch-tasks-stream-shutdown-emit.test.sh
PASS rc=0   18.7s tests/watch-tasks-stream-unready-holder-restamp.test.sh
=== 95fbd566a (origin/main) ===
FAIL rc=1    0.4s tests/watch-tasks-stream-shutdown-emit.test.sh
PASS rc=0   17.2s tests/watch-tasks-stream-unready-holder-restamp.test.sh
=== bbde097b8 (merge head, re-run) ===
FAIL rc=1    0.4s tests/watch-tasks-stream-shutdown-emit.test.sh
PASS rc=0   19.3s tests/watch-tasks-stream-unready-holder-restamp.test.sh
  • shutdown-emit fails on origin/main with the same three FAIL lines (leaving ... to the next watcher not logged) — pre-existing on this macOS host, not introduced here; main's Linux CI at 95fbd566a is green.
  • unready-holder-restamp step (e) was a one-off (pid re-stamp timing); it passed on all three re-runs.
Full per-suite list (first run, merge head)
PASS rc=0    2.8s tests/watch-tasks-stream-announce-survives-failure.test.sh
PASS rc=0   10.5s tests/watch-tasks-stream-bare-directory-event.test.py
PASS rc=0    6.8s tests/watch-tasks-stream-config-hot-reload-symlinked-workspace.test.py
PASS rc=0    5.9s tests/watch-tasks-stream-config-hot-reload.test.py
PASS rc=0    8.7s tests/watch-tasks-stream-dead-worker-reap.test.py
PASS rc=0   13.5s tests/watch-tasks-stream-dispatch-resolve-retry.test.sh
PASS rc=0   11.6s tests/watch-tasks-stream-fallback-receipt-scope.test.py
PASS rc=0    6.4s tests/watch-tasks-stream-foreign-instance-id-corrected.test.py
PASS rc=0   57.1s tests/watch-tasks-stream-handler-terminal-rc.test.py
PASS rc=0    2.9s tests/watch-tasks-stream-inbox-and-workspace-env.test.py
PASS rc=0    6.4s tests/watch-tasks-stream-interrupted-task-retry.test.py
PASS rc=0   26.4s tests/watch-tasks-stream-malformed-roster-row.test.py
PASS rc=0    2.4s tests/watch-tasks-stream-old-sentinel-access-error.test.sh
PASS rc=0    3.7s tests/watch-tasks-stream-pointer-migration.test.py
PASS rc=0    1.7s tests/watch-tasks-stream-pool-writer.test.sh
PASS rc=0    2.3s tests/watch-tasks-stream-priority-sweep.test.py
PASS rc=0   27.6s tests/watch-tasks-stream-readiness-window-decision-instant.test.py
PASS rc=0   39.7s tests/watch-tasks-stream-readiness-window-held-task-recovery.test.py
PASS rc=0   76.4s tests/watch-tasks-stream-readiness-window-unreadable-config.test.py
PASS rc=0   32.6s tests/watch-tasks-stream-role-session-kills-standby.test.sh
PASS rc=0   77.8s tests/watch-tasks-stream-self-check.test.sh
PASS rc=0    4.6s tests/watch-tasks-stream-sentinel-ownership.test.py
FAIL rc=1    0.4s tests/watch-tasks-stream-shutdown-emit.test.sh
PASS rc=0  185.0s tests/watch-tasks-stream-shutdown-leaves-inflight-claim.test.py
PASS rc=0   26.9s tests/watch-tasks-stream-start-lock.test.sh
PASS rc=0    8.8s tests/watch-tasks-stream-survives-sigurg-and-fswatch-death.test.sh
PASS rc=0   12.1s tests/watch-tasks-stream-sweep-does-not-block-new-tasks.test.py
PASS rc=0    4.5s tests/watch-tasks-stream-sweep-stale-sentinel-cost.test.sh
PASS rc=0    9.1s tests/watch-tasks-stream-trap-exit.test.sh
FAIL rc=1   21.8s tests/watch-tasks-stream-unready-holder-restamp.test.sh
PASS rc=0    3.8s tests/watch-tasks-stream-watchdog-trap-race.test.sh
PASS rc=0    0.4s tests/watcher-identity.test.py
PASS rc=0    0.7s tests/watcher-sentinel-naming.test.py
PASS rc=0    0.5s tests/watcher-tests-never-inherit-a-seat-env.test.py
PASS rc=0   42.4s tests/watcher-inbox-resolver.test.sh
PASS rc=0    1.1s tests/watcher-sentinel-ownership.test.sh
PASS rc=0    0.4s tests/watcher-shells-resolved-interpreter.test.sh
PASS rc=0   10.4s tests/watcher-writes-its-beat.test.sh
PASS rc=0    0.9s tests/answered-rule-parity.test.py
PASS rc=0   14.0s tests/claude-task-notifier-standby-log.test.sh
PASS rc=0    2.2s tests/delivery-pointer-retirement.test.py
PASS rc=0    9.7s tests/inbox-events-burst.test.py
PASS rc=0    1.5s tests/task-dispatch-sweep-plan.test.py
PASS rc=0   18.0s tests/worker-mode-is-gated-on-env.test.py
PASS rc=0    4.1s tests/task-dispatch-contract.test.py
PASS rc=0    0.1s tests/task-archive.test.py
PASS rc=0    0.2s tests/delivery-core-contract.test.py
PASS rc=0    0.2s tests/delivery-core-crash-between-terminal-and-release.test.py
PASS rc=0    4.0s tests/delivery-core-enforcement.test.py
PASS rc=0    0.2s tests/delivery-core-terminal-not-contended.test.py
PASS rc=0   21.4s tests/delivery-primitives-single-owner.test.py
PASS rc=0    0.1s tests/pool-beat.test.py
PASS rc=0    0.2s tests/pool-input-watch-per-seat.test.py
PASS rc=0    0.3s tests/pool-rearm-watcher-observe-and-remedy.test.py
PASS rc=0    0.1s tests/pool-record-single-owner.test.py
PASS rc=0    0.1s tests/pool-remedy-timer-ensured-on-spawn.test.py
PASS rc=0    0.2s tests/pool-router-non-exclusive-folder.test.py
PASS rc=0    2.1s tests/pool-router-replay-idempotence.test.py
PASS rc=0    0.0s tests/pool-supervision-fast-resume.test.py
PASS rc=0    0.0s tests/pool-supervision-watcher-rung.test.py
PASS rc=0    0.1s tests/pool-supervision-wedge-rung.test.py
PASS rc=0    0.0s tests/pool-supervision.test.py
PASS rc=0    0.2s tests/pool-suspension-shared-owner.test.py
PASS rc=0    0.2s tests/pool-wedge-card-ack-and-batching.test.py
PASS rc=0    0.3s tests/pool-wedge-cards.test.py
PASS rc=0    0.1s tests/pool-wedge-frame-change.test.py
PASS rc=0    0.1s tests/pool-wedge-remedy-refusals.test.py
PASS rc=0    0.1s tests/pool-work-outstanding-real-sentinels.test.py
PASS rc=0    0.1s tests/worker-auth-state.test.py
PASS rc=0    0.3s tests/worker-delivery-matches-the-hook.test.py
PASS rc=0    0.1s tests/worker-pool-design-retractions.test.py
PASS rc=0    0.1s tests/worker-pool-design-transitions.test.py
PASS rc=0    4.1s tests/check-pending-tasks-worker-pool.test.sh
PASS rc=0    0.1s tests/core-never-names-the-worker-pool-skill.test.py
PASS rc=0    0.7s tests/skills/worker-pool/codex-worker-auto-reset-launcher.test.py
PASS rc=0    0.3s tests/skills/worker-pool/create-worker-command.test.py
PASS rc=0    0.7s tests/skills/worker-pool/create-worker-concurrency.test.py
PASS rc=0    2.4s tests/skills/worker-pool/picker-apply-concurrency.test.py
PASS rc=0    5.2s tests/skills/worker-pool/pool-advertise.test.py
PASS rc=0    1.2s tests/skills/worker-pool/pool-ask.test.py
PASS rc=0    0.1s tests/skills/worker-pool/pool-attribution.test.py
PASS rc=0    0.1s tests/skills/worker-pool/pool-bindings-roster.test.py
PASS rc=0    0.4s tests/skills/worker-pool/pool-delivery.test.py
PASS rc=0    0.3s tests/skills/worker-pool/pool-inbox-resolver-access-error.test.py
PASS rc=0    2.5s tests/skills/worker-pool/pool-inbox-resolver.test.py
PASS rc=0    0.1s tests/skills/worker-pool/pool-remedy-ensures-supervisor.test.py
PASS rc=0    0.2s tests/skills/worker-pool/pool-remedy-timer.test.py
PASS rc=0    0.7s tests/skills/worker-pool/pool-remedy.test.py
PASS rc=0    1.4s tests/skills/worker-pool/pool-restart.test.py
PASS rc=0    0.3s tests/skills/worker-pool/pool-route-handler.test.py
PASS rc=0    1.2s tests/skills/worker-pool/pool-router-pass.test.py
PASS rc=0    0.4s tests/skills/worker-pool/pool-sessions.test.py
PASS rc=0    0.3s tests/skills/worker-pool/pool-supervise.test.py
PASS rc=0    0.1s tests/skills/worker-pool/pool-suspend-resume.test.py
PASS rc=0    0.4s tests/skills/worker-pool/profile-label-overrides.test.py
PASS rc=0    0.1s tests/skills/worker-pool/publish-handler-raises-on-the-real-module.test.py
PASS rc=0    0.1s tests/skills/worker-pool/remedy-timer-runner-compatibility.test.py
PASS rc=0    0.4s tests/skills/worker-pool/rename-worker.test.py
PASS rc=0    0.1s tests/skills/worker-pool/roster-write-publishes-advertisement.test.py
PASS rc=0    0.5s tests/skills/worker-pool/spawn-worker-launcher.test.py
PASS rc=0    0.2s tests/skills/worker-pool/spawn-worker-resume.test.py
PASS rc=0    0.4s tests/skills/worker-pool/spawn-worker-runtime-and-sentinel.test.py
PASS rc=0    0.1s tests/skills/worker-pool/transcript-path-recorded.test.py
PASS rc=0    0.6s tests/skills/worker-pool/worker-bootstrap-decision.test.py
PASS rc=0    0.4s tests/skills/worker-pool/worker-identity-records.test.py
PASS rc=0    0.2s tests/skills/worker-pool/worker-picker-commands.test.py
PASS rc=0   15.7s tests/skills/worker-pool/launch-worker-session-env-forwarded.test.sh
PASS rc=0   60.9s tests/skills/worker-pool/worker-watcher-supervisor.test.sh
PASS rc=0    0.1s tests/sync-from-src-repo-relative.test.py
PASS rc=0    0.6s tests/task-queue-counts-the-announcing-inbox.test.py

@qingyun-wu
qingyun-wu requested a review from marklysze October 7, 2026 01:38
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Gate

✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 93%.

Diff Coverage

Diff: origin/main...HEAD, staged and unstaged changes

  • packages/ag2-sparrow/ag2_sparrow/remote_gateway_bridge.py (100%)
  • skills/worker-pool/scripts/resolve_inbox_entry.py (100%)
  • src/delivery/task_dispatch.py (99.1%): Missing lines 623
  • src/line_relay.py (100%)
  • src/task_archive.py (100%)

Summary

  • Total: 289 lines
  • Missing: 1 line
  • Coverage: 99%

src/delivery/task_dispatch.py

Lines 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)

@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

CI on bbde097b8: 32 pass, 2 skipped (publish, Windows runtime — as on every PR), 1 fail. All 7 python shards and all 3 shell tail shards are green now (the 4 python shards that were red at 3e341803 were main's, cleared by the base merge).

The failing check is the required diff coverage >= 95% (python) gate — its first verdict on this PR, since it was skipped while the shards were red:

Diff: origin/main...HEAD
packages/ag2-sparrow/ag2_sparrow/remote_gateway_bridge.py (100%)
skills/worker-pool/scripts/resolve_inbox_entry.py (72.7%): Missing lines 68-71,88-89
src/delivery/task_dispatch.py (62.7%): Missing lines 177-178,199,203-204,237-238,247-248,252-253,539-544,546-558,561-562,566-570,585
src/line_relay.py (0.0%): Missing lines 10,12-15,18-21,23-30,32-42,45-50
src/task_archive.py (91.6%): Missing lines 239,283-285,320-321,329-330,347-348
Total: 280 lines   Missing: 88 lines   Coverage: 68%

Every missing range is code the suites reach only as a child process of the watcher's bash (line_relay.py behind fswatch in src/inbox-events.sh, task_dispatch.py sweep-plan and resolve_inbox_entry --batch from startup_sweep), plus the OSError branches in task_archive.retire_*. coverage.py does not see those children, so the gate needs direct unit tests for line_relay, _sweep_plan_cli / _resolve_batch / _is_answer, and resolve_inbox_entry.batch. That is a change to the PR's own test set, not to the merge, so I have not added it here — flagging it for the owner's call.

@marklysze marklysze left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: main is contained in the head, and the only conflict is _USAGE in src/delivery/task_dispatch.py. main's revert of #4303 had dropped [--typable] from pending-candidates and next-pending; the resolution keeps the new sweep-plan line and takes main's text for the other two. git diff --numstat origin/main HEAD -- src/delivery/task_dispatch.py is 145 0 (purely additive), and --typable occurs 0 times in the head and in main.
  • #5170 (+411/-10, 11 files): src/line_relay.py (new), src/inbox-events.sh (new launcher), the watcher's one-time retire-archived-pointers run, 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-sweep and watch-tasks-stream-sweep-does-not-block-new-tasks. watch-tasks-stream-readiness-window-decision-instant fails on this host, identically on origin/main, on 3e341803 and 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_once is called from sweep_entries, which startup_sweep runs in a background subshell, so a slow migration cannot block a new task. $! still names fswatch, not the relay, under macOS /bin/bash 3.2.57 (I tested the same cmd > >(proc) & shape).
  • Mutation, 9 decisions, 6 caught: fswatch writing straight to the FIFO (no relay), the relay not draining at once, the shell read clearing 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)

  1. 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.
  2. An unterminated last line being dropped by the relay passes.
  3. 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>
@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

Coverage follow-up to the main merge (bbde097b8): the one red required check was diff coverage >= 95% (python), because every missing range was code the suites reach only as a child of the watcher's bash, which coverage.py cannot see. c646419a4 adds one standalone test, tests/watcher-sweep-paths-in-process.test.py, that imports the modules and drives those paths in-process. Test-only; no production change.

Before — the gate's verdict on bbde097b8:

skills/worker-pool/scripts/resolve_inbox_entry.py (72.7%): Missing lines 68-71,88-89
src/delivery/task_dispatch.py (62.7%): Missing lines 177-178,199,203-204,237-238,247-248,252-253,539-544,546-558,561-562,566-570,585
src/line_relay.py (0.0%): Missing lines 10,12-15,18-21,23-30,32-42,45-50
src/task_archive.py (91.6%): Missing lines 239,283-285,320-321,329-330,347-348
Total: 280 lines   Missing: 88 lines   Coverage: 68%

After — the gate's own method locally (coverage run --rcfile=.coveragerc per file → coverage combine → coverage xml → diff-cover coverage.xml --compare-branch=origin/main --fail-under=95), run over the new test plus the PR's five directly related Python suites, with every SUTANDO_*, TMUX, AGENT_ID, AG2_AGENT_NAME unset and a temp HOME:

packages/ag2-sparrow/ag2_sparrow/remote_gateway_bridge.py (0.0%): Missing lines 296,3703-3704
skills/worker-pool/scripts/resolve_inbox_entry.py (100%)
src/delivery/task_dispatch.py (100%)
src/line_relay.py (100%)
src/task_archive.py (100%)
Total:   280 lines
Missing: 3 lines
Coverage: 98%

The three remaining misses are in remote_gateway_bridge.py, which the full CI suite already measured at 100% on bbde097b8 (its tests are outside this six-file subset); the four target modules went 0–92% → 100% on changed lines. coverage report -m on the same data: line_relay.py 100%, resolve_inbox_entry.py 98% (only line 51, the EACCES retry branch, which is not a changed line), and none of the previously listed lines remain in task_dispatch.py / task_archive.py.

What the new file covers: line_relay.relay (lines leave whole, a missing final newline is added, EOF with no input, stdin drained to memory while stdout is not being read — 2.4 MB through a pipe pair) and main (copy + BrokenPipeError swallowed); _sweep_plan_cli via td._main(["sweep-plan", ...]) (too few args, unknown/dangling option, non-numeric window/timeout, empty inbox → only the done line, counts line on stderr, the real pool resolver dropping a stale pointer); _resolve_batch (unrunnable, hanging, non-zero, missing header, malformed lines) and _is_answer (no refusal prefix, not in live listing, empty body, unreadable ready body); sweep_plan (missing results dir, entry the batch said nothing about, entry removed under the sweep, injected clock); resolve_inbox_entry.batch/main (hit, no-payload, undelivered, non-sentinel, directory at the payload, empty batch, --batch from stdin, single-entry hit/miss, usage errors); task_archive.retire_delivery_pointers / retire_archived_pointers (unlistable root, missing root, FIFO at .lock → "not a regular file" logged, missing tasks/archive, missing deliveries root).

Test run — the new file plus every test file this PR touches, clean env, macOS:

$ python3 tests/watcher-sweep-paths-in-process.test.py
Ran 34 tests in 2.311s
OK
$ bash scripts/review-checks.sh   # over the PR diff + the new file
review-checks: PASS (hardcoded-paths + root-artifacts + prose-cap clean)

33 PR-touched files: 30 pass on first run. claude-task-notifier-standby-log.test.sh failed once and passed on re-run (timing). watch-tasks-stream-shutdown-emit.test.sh fails identically on an origin/main checkout on this host, so it is host-specific here (green on CI). One to note for the PR itself, not this commit: watch-tasks-stream-bare-directory-event.test.py's "EOF on the event stream relaunches fswatch, the watcher stays" case failed 2/2 on this branch on macOS and passes on origin/main here; it was green on the ubuntu runners at bbde097b8. I have not diagnosed it; flagging so it is on the record.

CI on c646419a4 is running; I will report the final check conclusions.

…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 marklysze left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) and watch-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's BrokenPipeError handling removed (5 of 5 caught, with the three-file set watcher-sweep-paths-in-process, pointer-migration and inbox-events-burst).
  • My approximation of the gate: I ran coverage over 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) and diff-cover against origin/main: resolve_inbox_entry.py, task_dispatch.py and task_archive.py at 100%, line_relay.py at 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, ruff and 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).

@qingyun-wu

Copy link
Copy Markdown
Collaborator Author

CI result. The first run on c646419a4 lost shard 3/7 to a pip read timeout against files.pythonhosted.org (zero tests ran there), and its re-run was cancelled by the PR concurrency group when fc6e5623c (additive line_relay write-shape tests on the same file) was pushed on top. On fc6e5623c: 34 check-runs, 32 success, 2 skipped (publish, Windows runtime — as on every PR), 0 failures. The required diff coverage >= 95% (python) gate is green:

Diff: origin/main...HEAD
packages/ag2-sparrow/ag2_sparrow/remote_gateway_bridge.py (100%)
skills/worker-pool/scripts/resolve_inbox_entry.py (100%)
src/delivery/task_dispatch.py (100%)
src/line_relay.py (79.4%): Missing lines 24-30
src/task_archive.py (100%)
Total:   280 lines   Missing: 7 lines   Coverage: 97%

(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 liususan091219 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-tasks PASS, delivery-pointer-retirement 13 OK, task-dispatch-sweep-plan 12 OK, watcher-sweep-paths-in-process 37 OK, watch-tasks-stream-pointer-migration PASS, answered-rule-parity PASS, watcher-tests-never-inherit-a-seat-env PASS, inbox-events-burst PASS, task-dispatch-contract 104 OK, watch-tasks-stream-priority-sweep PASS.
  • Failure mode 1: I put watch-tasks-stream.sh, task_dispatch.py and resolve_inbox_entry.py back 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_pointers call in task_archive.archive_file. test_archive_file_retires_the_pointer_in_the_same_step fails. 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 voice archiveFile in task-bridge.ts is 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-pointers once 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 liususan091219 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.py and packages/ag2-sparrow/ag2_sparrow/remote_gateway_bridge.py, plus docs/catalog.json and 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 fc6e5623c would not cover the head that merges.
  • The feature is not on main by another route: retire_delivery_pointers and sweep-plan do not appear there.

Unchanged from my last review

  • The code at fc6e5623c met 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 liususan091219 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 fc6e5623c against main 02cb7e8c (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, plus docs/catalog.json, docs/src-map.md and 8 watcher test files.
  • Since my last comment, main changed 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: main still has no retire_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_answer parity) 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
@qingyun-wu
qingyun-wu requested a review from marklysze November 3, 2026 07:03

@liususan091219 liususan091219 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. 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:56 and tests/watch-tasks-stream-sweep-does-not-block-new-tasks.test.py:94 start a watcher outside popen_bound or bind_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 failed test_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.

  2. It conflicts with main again. After #6151 and #6131, git merge-tree against e156b66a conflicts only in CHANGELOG-PENDING.md, so this one is trivial.

  3. A fresh expert review of the resolved watcher merge. marklysze's approvals are at fc6e5623 and 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)

qingyun-wu and others added 2 commits November 5, 2026 21:39
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>
@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

@cla-assistant check

@liususan091219 liususan091219 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. CI is green at this head (48 success, 3 skipped, 0 failing): the hermetic-env-structural and watcher-fixtures-are-bound guards that failed the PR's own new tests pass now. The fix is aa1d0298 (+11/-6, tests only): tests/delivery-pointer-retirement.test.py runs under isolate_os_environ() and gives its CLI subprocess hermetic_env(); tests/watch-tasks-stream-pointer-migration.test.py and tests/watch-tasks-stream-sweep-does-not-block-new-tasks.test.py start the watcher through popen_bound instead of a bare Popen(..., 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.
  2. No conflict with main. b7ea1766 merges main (f31d8190); GitHub reports the head MERGEABLE, 1 commit behind. Against main the PR is +152/-0 in src/delivery/task_dispatch.py, +56/-24 in src/watch-tasks-stream.sh, +6/-2 in packages/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_text sites in the tests remain an extracted-and-executed shell function (handler_result_is_answer parity), an ast-extracted real _archive_task_file run 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)

This branch has not been deployed

No deployments
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.

3 participants