molecule-core/platform/internal
rabbitblood 73171532a1 feat(memory): optimistic-locking via if_match_version on workspace_memory writes
Closes the silent-overwrite hole where two agents racing a read-modify-
write on the same memory key left only one agent's update. Relevant for
orchestrators (PM, Dev Lead, Marketing Lead) keeping structured running
state (delegation-result ledgers, task queues) in memory, and for the
``research-backlog:*`` keys that multiple idle loops write in parallel.

## Semantics

### Back-compat path (no if_match_version)
Unchanged: ``INSERT ... ON CONFLICT UPDATE`` last-write-wins. Every
existing agent tool, every existing ``commit_memory`` call, every
existing cron that writes memory — all continue to work with no edit.

### Optimistic-lock path (if_match_version set)
1. Client calls ``GET /memory/:key`` → ``{value, version: V}``
2. Client modifies value locally
3. Client ``POST /memory {key, value, if_match_version: V}``
4. Server: ``UPDATE ... WHERE version = V`` + RETURNING new version
5. On match → 200 + ``{version: V+1}``
6. On mismatch → 409 + ``{expected_version: V, current_version: <actual>}``
7. Client reads the actual version and retries.

### Create-only marker
``if_match_version: 0`` means "create iff the key doesn't exist yet".
Two agents simultaneously seeding a shared key will see exactly one
success + one 409 — no silent collision, no duplicate-init work.

### Schema

Migration 023 adds ``version BIGINT NOT NULL DEFAULT 1``. Existing rows
baseline at 1. New rows start at 1. Every successful write (both paths)
increments: ``version = version + 1`` on update, ``1`` on insert.

## Why version, not updated_at

``updated_at`` has second-granularity and can collide between concurrent
writers on a fast clock. A monotonic counter is collision-free and more
readable in the 409 response body ("expected 5, current is 7 — you
missed 2 writes" tells an agent exactly what to re-read).

## Why ``if_match_version`` and not an ETag header

JSON field keeps it in the request body, visible alongside the value
payload. Agents assembling requests programmatically don't have to
remember to thread a header through their HTTP client wrapper; the
existing ``commit_memory`` tool can grow one optional kwarg and match
the existing signature shape.

## Tests

11 memory-handler cases covering every path:
- GET list / get (with version in response shape)
- Set with no version (back-compat upsert, returns new version)
- Set with if_match_version match (happy path, increment)
- Set with if_match_version mismatch (409 + expected/current fields)
- Set with if_match_version=0 on absent key (create-only success)
- Set with if_match_version=N on absent key (409 — caller's mental
  model is wrong)
- Bad inputs (missing key, malformed JSON)
- Delete happy + error path

Full ``go test ./internal/handlers/`` green.

## Follow-up (not in this PR)

- Workspace-template tool update: ``commit_memory(content, *,
  if_match_version=None)`` surfaces the new option + on 409 surfaces
  the current_version so agents can retry without manual re-read.
- Named checkpoints table (``workspace_checkpoints``) for durable
  orchestrator state snapshots. Different concern than per-key locking;
  separate PR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 02:32:46 -07:00
..
bundle initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
channels fix(security): scope PausePollersForToken to requesting workspace (closes #329) 2026-04-15 21:22:50 -07:00
crypto initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
db fix(liveness): raise workspace TTL 60s → 180s to survive Opus synthesis (#386) 2026-04-16 00:05:45 -07:00
envx initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
events initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
handlers feat(memory): optimistic-locking via if_match_version on workspace_memory writes 2026-04-16 02:32:46 -07:00
metrics initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
middleware chore(test): remove dead constants from wsauth_middleware_test.go (#358) 2026-04-16 05:02:11 +00:00
models initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
plugins initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
provisioner config(org): add Telegram to Dev Lead and Research Lead (#385) 2026-04-16 00:00:10 -07:00
registry fix(registry): allow ancestor↔descendant A2A so audit_summary can reach PM 2026-04-14 22:18:38 -07:00
router fix(#249): add /schedules/health endpoint accessible to CanCommunicate peers (#400) 2026-04-16 00:45:30 -07:00
scheduler fix(code-review): CanvasOrBearer fall-through, scheduler short(), activity spoof log + 6 new tests 2026-04-15 11:48:25 -07:00
supervised fix(platform): panic-recovering supervisor for every background goroutine (#92) 2026-04-14 20:34:18 -07:00
ws initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
wsauth fix(security): close WorkspaceAuth fail-open on non-existent workspace IDs (#318) 2026-04-15 21:02:29 -07:00