Parallel Agent writes frequently trigger 'Space modified elsewhere' conflicts
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 144
- Forks
- 18
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 43
Description
Status — Partially resolved
Phase 1 merged in #103 on 2026-08-15. It fixes the common false global Reload state caused by stale structure-save acknowledgements racing Canvas Sync: versions now reconcile monotonically, covered 409 responses retry automatically, and a later SSE update can clear an earlier conflict and reschedule the save.
The issue remains open. Follow-up work still includes unified snapshot/update-gap reconciliation, dirty-safe reconnect behavior, delta-log backfill, browser structure writes joining the server write coordinator, isolation of derived writes, and node/field-scoped genuine conflict handling.
Problem
With multiple Agent Nodes running in parallel against the same Space, the browser frequently shows:
This Space was modified elsewhere. Your recent edits won't be saved.
The warning offers a Reload action, and it can recur often enough that concurrent Agent work becomes disruptive. The Space otherwise appears to update smoothly until the conflict is raised.
Observed conditions
- Multiple Agent Nodes / external Agents write to the same Space concurrently.
- The frequency appears higher after recent real-time sync and Preview Workspace changes.
- Relevant recent merges to evaluate:
- #94 — unified Headless/RFS Agent turn streaming, Canvas Sync handshake/heartbeat/reconnect, snapshot convergence
- #82 / #91 — unified tabbed Preview Workspace and subsequent workspace/canvas interaction changes
No causal attribution to these PRs is confirmed yet. PR #94 is the primary regression boundary because it directly changed Canvas Sync; #82/#91 should be ruled in or out through history/bisect rather than assumed responsible.
Current behavior in code
- Structure saves use the local Canvas
version. A stale save receivesCANVAS_VERSION_CONFLICT(HTTP 409), sets a stickyversionConflict, blocks subsequent autosaves, and asks the user to reload. canvasSyncStoreapplies an update only whenfromVersion === local version.- If the client detects a version gap and has no pending content edits, it calls
loadCanvas()to converge. - If pending content edits exist, gap healing is intentionally skipped to avoid clobbering local work; the next autosave is allowed to reach the existing 409 arbitration path.
- There is not yet delta-log backfill for incremental gap healing, per
docs/architecture/canvas-realtime-sync.md.
With several Agents rapidly advancing the server version, a missed, delayed, reordered, or reconnect-boundary update can therefore leave the tab stale. Local autosave then turns that stale baseline into the persistent warning/reload flow.
Investigation requested
Instrument and reproduce parallel Agent writes while recording:
- client Canvas version before/after every snapshot and update
- each event's
fromVersion/toVersion - reconnect and snapshot boundaries
- pending content-node IDs and pending structure saves
- every
loadCanvas()invocation and concurrent load - structure PUT expected/server versions on 409
- originating Agent/thread and server write order
Determine whether conflicts come from:
- genuinely concurrent user edits that require arbitration
- dropped/reordered SSE updates or reconnect gaps
- overlapping snapshot-driven and primary Canvas loads
- local structure autosaves racing Agent broadcasts
- derived writes such as height/preprocessing saves
- a regression introduced by #94
Expected
- Parallel Agent writes that target unrelated nodes should converge without forcing a full browser reload.
- Recoverable version gaps should heal automatically.
- Genuine same-field conflicts should preserve local work and provide targeted resolution rather than globally disabling all Canvas autosaves.
- The warning should be rare and explain which write conflicted.
Acceptance criteria
- A deterministic multi-Agent reproduction test exists.
- Unrelated Agent writes no longer cause a global sticky version conflict.
- Reconnect/snapshot boundaries converge without a spurious structure PUT or 409.
- Genuine conflicts remain lossless and actionable.
- Regression coverage includes several concurrent Agent/Headless writers and a browser tab with pending local content.
Related
- #94 — recently merged real-time sync work and primary regression boundary
- #97 — slow browser refresh; separate root cause, but this issue amplifies it by repeatedly requiring Reload
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read docs/architecture/canvas-realtime-sync.md and trace canvasSyncStore, loadCanvas(), and the structure PUT conflict path. Instrument the listed version, reconnect, pending-edit, and write-order events, then reproduce concurrent Agent writes. Done means a deterministic test covers parallel writers, reconnect and snapshot boundaries, pending local content, and targeted handling of genuine conflicts without a global sticky conflict.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- distributed-systems, frontend, testing-qa
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100