fix(sync): bound OpenCode watcher fallback work - #1306
Conversation
roborev: Combined Review (
|
14fd722 to
d989ab5
Compare
roborev: Combined Review (
|
roborev: Combined Review (
|
99ebf73 to
48cb222
Compare
roborev: Combined Review (
|
48cb222 to
d82b807
Compare
roborev: Combined Review (
|
d82b807 to
d5778a3
Compare
roborev: Combined Review (
|
d5778a3 to
39dd34f
Compare
roborev: Combined Review (
|
39dd34f to
9f5af1d
Compare
roborev: Combined Review (
|
9f5af1d to
58ca00f
Compare
roborev: Combined Review (
|
58ca00f to
ff3a1f6
Compare
roborev: Combined Review (
|
ff3a1f6 to
3deffac
Compare
roborev: Combined Review (
|
3deffac to
f41d684
Compare
roborev: Combined Review (
|
f41d684 to
6439e2b
Compare
roborev: Combined Review (
|
6439e2b to
8617ffb
Compare
roborev: Combined Review (
|
8617ffb to
93fbe71
Compare
roborev: Combined Review (
|
2c58a73 to
add0b84
Compare
roborev: Combined Review (
|
add0b84 to
c41bab0
Compare
roborev: Combined Review (
|
c41bab0 to
2abf622
Compare
roborev: Combined Review (
|
2abf622 to
38d5abc
Compare
roborev: Combined Review (
|
38d5abc to
9d14f2a
Compare
roborev: Combined Review (
|
9d14f2a to
43dc456
Compare
roborev: Combined Review (
|
43dc456 to
07c52e8
Compare
roborev: Combined Review (
|
07c52e8 to
5fac113
Compare
roborev: Combined Review (
|
|
Watching this with great interest @rodboev . My two cents is that I probably would have done a much purer queuing implementation. Watchers/pollers who dump work in the queue, and job workers who work on the queue, and probably have done this through a db queue, instead of in memory. If you are interested, you can see that implemented in ready-for-agent. So I have a job queue, implementing fairly standard queue semantics, and rely on db locking for queue semantics. Queue implementation is an Effect TS service. I suppose this isn't easy to read if you don't know TypeScript or use Effect TS (that's FP for TypeScript). But I've found this pattern to be very stable, and survives restarts, which an in-memory implementation doesn't. For agentsview that's not needed, but still nice to have fast starts. |
|
Agreed that the OpenCode journal feed should be a pure package. I am thinking of a split along these lines, listed in merge order; let me know what you think:
B and C ship as one PR, since C is B's only consumer. C is essentially the queue model you're suggesting: watchers and pollers enqueue keyed work, serialized workers apply it and ack after the archive write. In-process still makes more sense than db-backed, since OpenCode's journal is already the durable log and startup reconciliation the recovery path; persisting the queue would just add a second acknowledgement protocol. The Effect TS service isn't importable from Go, but the semantics (unique keyed jobs, transactional claiming) are good reference points. Thanks for pointing that out. |
|
It looks like #1291 takes out most of the reason for B and C; once it's merged, unchanged sessions will get skipped, and an idle pass over a large container becomes cheap. So based on that:
|
|
@rodboev looking at #1291 I feel it gets into the same issue I ran into with my third implementation. The code review never converges, and the code writing agent never solves all the problems you still have as its an architectural issue. My hunch is that your first slice also won't converge, as you than run into the 2 minute poll / max time (I can't exactly describe the issue, but it feels this is what I still ran into). So I would have pure work generators, which read the two opencode histories I know about (the old one and the new .db one), but do no work. They simply write session ids or session id ranges. These work generators keep scanning or polling. Then the job workers receive a range of session ids and read/parse them, and put them into agentsview. One can control the cpu/load of a session parser by the number of sessions it does at a time. Although for super large sessions this could still be substantial. My first slice: write the work generators, queue and job worker purely for the old style opencode sessions, so that work gets removed from the normal path. If roborev is happy with this slice, the second step would be to move the modern opencode v1 db into this architecture. The advantage of slice 1 is that this is old style, so you don't need to write the code to keep rescanning/polling this, we can be sure this is just importing old history. For slice 2 you then add the continual scanning for updates, but you have the queue architecture already in place. Again I'm just sketching here, I don't know the agentsview code base nor the opencode architecture too well, this is just based on seeing my clanker attempting to solve this three times and running into one issue after another |
|
@rodboev : I let the clanker write up a description of opencode's storage format. I think it's more useful detail than session-format-sources.md, but not sure you want this in your docs so haven't created a PR for this. And the Xplain db format is extremely concise, and I can read this extremely fast and well, but it's very niche. But because it hides the details I find it great for humans. I think this explains my hunch for slice 1 well: drop the json reading from the current path, do it via the new queue mechanism, and then you're done forever (except when you request a full scan). Slice 2 is then fixing the issue that changing one session causes an entire repass. And hopefully this can fit nicely onto the new queue. Please let me know if you want me to help out anywhere. |
|
I think the 2 minute poll / max time is in I added that slice as D above. It lands ahead of A and keeps a budget-starved root off the poller; completion-based scheduling is in C. The writeup is useful, thanks. Backfilling the JSON tree and treating the root as SQLite-only works makes sense for OpenCode, but I don't think it extends to the forks: my Kilo and MiMoCode ship the journal tables empty and still write The backfill also flips hybrid shadowing, since the DB row would win where JSON does today. That plus the per-root state can be a follow-up slice; I'm trying to keep this reviewable. |
|
Yes good point, db backfill shouldn't overwrite json. But implicitly I also wanted to improve the startup time of agentsview, so it doesn't start with a full scan, this simply gets scheduled when the parsing of all agents move to this architecture. My opencode is 27GB now, and I'm getting better at this stuff, aiming for a consistent 150+ PRs a week, so startup time is going to be a real issue. |
|
Yeah, I'm in the same boat with tens of GB of DBs spread across various harnesses. I would also like to see it queue things up in the background. The desktop app is much slower than having I've slowed down but my rate was about the same at the 30-day mark with around 500 opened. I manually trim history to last 2 weeks to keep it manageable between agentsview and my hand-rolled history viewer. Sometimes this is the reason I use the hand-rolled one though — it caches on startup, but I also don't particularly care about the RAM hit. |
|
I run it with agentsview serve. Just doesn't work. But my history goes all the way back to aider days. There's a lot. And I feel agentsview can potentially give me a lot of valuable data, and simply tracking improvements over time is already valuable. |
OpenCode roots that miss recursive watcher coverage currently enter the generic two-minute unwatched-root poller. Each wake runs authoritative root reconciliation and lists the shared SQLite archive, and a pass longer than the interval can consume an already queued wake immediately after completion. Large installations can therefore sustain archive-scale reads and CPU while idle.
This carries provider-owned coverage units from watch planning into native and degraded scheduling. OpenCode's SQLite unit uses the event journal to select a bounded batch of settled session IDs, resolves each ID through targeted metadata, and feeds the existing provider parse path. The coordinator permits one active pass per coverage key, coalesces wakes, and schedules the next pass from completion. Other providers retain provider-scoped reconciliation.
Journal rows, payload bytes, pending IDs, ready IDs, and pass time have explicit caps. Checkpoints advance only after archive writes, retain work committed during an audit, and require another audit when the SQLite container is replaced. Unsupported, removed, or oversized journal state requests one provider-scoped authoritative audit and latches repeated journal work. Missing probes, recursive symlink gates, shared generic watch roots, storage canonicality, persistent archives, startup repair, and global overflow recovery keep their existing authority. The change adds no database schema, mirror, frontend, or server API surface.
The watcher-exhaustion trace and production CPU/read measurements came from @berenddeboer's issue report.
Closes #1208