fix(sync): bound OpenCode background work across watcher and polling modes - #1262
fix(sync): bound OpenCode background work across watcher and polling modes#1262rodboev wants to merge 20 commits into
Conversation
roborev: Combined Review (
|
roborev: Combined Review (
|
|
Thanks @rodboev for having a go at this. As you can see this is a massive PR. I don't know the code, but it doesn't feel right. What about having agentsview startup with sync, then having a queue and job workers to sync things in the background? It just feels to me this needs a different model. But then I don't know the code :-) |
roborev: Combined Review (
|
roborev: Combined Review (
|
|
Fair question. By raw count a good chunk of this is tests, but more to the point it's not a new model: it's deliberately the smaller half of the design already written up in #1208. Keep provider-owned watch units, add a cheap freshness probe so degraded polling and no-op watcher events skip authoritative work when nothing changed. Real changes still flow through the existing sync path, so there's no second runtime to reason about. The bounded journal feed from that design, which bounds active-use work as well, is the natural next slice, so I'll switch this from Closes to Part of #1208 to make that explicit. On startup sync plus a queue and job workers: the design writeup in the issue argues against durable queues since they couple source progress to archive writes, and probe-then-escalate gets the same idle-cost result without a new subsystem. I'm also simplifying how the probe is carried through watcher fallback, which should shrink the non-test diff before this is ready. |
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
ac59c6b to
3ce6653
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
3ce6653 to
80b2389
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
7a5614f to
9869a76
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
7778eeb to
2b69ddc
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
|
@wesm I just pushed the class fix on this branch. The invariant is now: degraded work stays probe-gated, real changes still flow through the existing sync path, and the journal change feed stays deferred to a follow-up under #1208. I also tightened provider polling to the exact selected configured roots, so an unchanged nested OpenCode scope does not get pulled back in by an overlapping parent. If this is the right shape, great. If the size is the real problem, the watcher no-op suppression half is separable and I can split that out, but I held that in reserve instead of guessing. Would you want this kept together or split? |
roborev: Combined Review (
|
31f83b3 to
e4a1430
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
|
Going to get this one finished, then take a look at the whole of it and decide whether a refactor is appropriate... |
roborev: Combined Review (
|
|
Splitting this up as @berenddeboer mentioned. The scheduler here is hand-rolled and would be properly done as a work queue. The argument against a durable queue still stands for now since it couples source progress to archive writes; an in-process queue with job workers avoids most of those problems. Two PRs:
Both will ref instead of close. |
Degraded polling now retains provider-owned watch scopes end to end. Persistent polling reasons carry their originating watch scopes, so a shared configured dir keeps a single complete owner when one exists and falls back to generic only for mixed or incomplete ownership. Provider-scoped polling reconciles only the exact selected configured roots, which keeps an unchanged nested OpenCode scope suppressed while a changed parent scope still syncs through the existing provider path.
One predicate now answers whether a pass reaches a configured root, and both discovery admission and tombstone accounting call it. A provider-scoped pass therefore reports only the roots it actually selected as covered, so a session relocated into a configured nested root the pass did not select is not read as deleted; that tombstone waits for a pass covering every configured root. Descendant scopes a pass excludes are excluded by the ownership query rather than after its rows are paged, so a small selected scope no longer does work proportional to a large unselected nested archive.
Each provider-owned directory carries its own polling obligation once a reason covers more than one, matching what the runtime watcher fallback already did. One obligation carries one probe, so a probe no longer answers for a directory it never observed. Obligation keys are namespaced by the reason that created them, which keeps two reasons on the same root distinct without either having to be split per directory to tell them apart.
The group-scoped acknowledgement reset from the prior rebuild stays intact, and every eligible polling group still runs in deterministic order. The bounded journal change feed remains deferred to a follow-up for issue #1208.
Part of #1208