Type: enhancement / feature request
Summary
Add a conditional-write (compare-and-swap) precondition to mutate so a client can say "apply this write only if the graph/branch/table has not advanced past version N (or commit <id>), otherwise reject it". Today the OCC token needed for CAS is readable but not enforceable on write, so a single-round-trip store-level compare-and-swap is impossible from the client.
Current surface (verified against the 0.8.x binary)
- An optimistic-concurrency token exists and is exposed:
omnigraph commit list --json → per-commit graph_commit_id (ULID) + monotonic manifest_version.
omnigraph snapshot --json → branch manifest_version and per-table table_version.
- Each write advances these.
- But there is no conditional-write primitive.
omnigraph mutate accepts no --if-version / --expected-commit / precondition flag, and the query engine cannot express a compound ... WHERE status = 'open' guard inside the mutation itself.
- Lance OCC conflicts do surface on a genuinely-raced manifest version (
stale view / manifest table version), but that only fires when two writes collide on the exact same base version — it is a race detector, not a value/precondition guard, and it cannot express "reject unless HEAD is still at the version I read."
Net: a client cannot ask the store to "set this field only if the row/branch is still in the state I read," and have the store atomically reject a lost race.
Requested behavior
A precondition on mutate (and ideally load) that the engine checks at commit time and rejects if unmet. Any one of these shapes would unblock us; listed roughly in order of preference:
-
Version/commit precondition (coarse, simplest):
omnigraph mutate --server <url> --graph <id> \
--if-manifest-version <N> # reject if branch manifest_version != N
# or: --if-commit <graph_commit_id>
Reject with a distinct, machine-detectable error (distinct exit code or a stable error token in the JSON body — not just a message substring) when the branch has advanced past <N> / <id>.
-
Per-table version precondition: same idea keyed on the table_version from snapshot (--if-table-version <table>=<v>), so an unrelated write to a different table doesn't spuriously fail the CAS.
-
Conditional mutation guard (finest, most work): let the mutation itself carry a WHERE-style predicate evaluated atomically at commit (SET status='in_progress' ... IF status='open'), so the precondition is on the row's value rather than a global version. This is the strongest form and removes the read-then-CAS round trip entirely.
The coarse version-based form (1) is enough for our use case.
Use case / motivation
We use omnigraph as a shared, multi-writer work-coordination graph (an MCP "task tracker" backing parallel AI agents and multiple humans). task_claim is the mutex: read a task, and if it is unclaimed, write status=in_progress, assignee, claimed_at.
On a local .omni store an advisory flock serializes writers, so this is safe. But in the deployed topology the store is a shared omnigraph-server over http(s)/S3, where the local lock cannot coordinate across pods. Two agents that both read a task as open both write their own claim; without a store-enforced precondition, last-write-wins silently clobbers the first claimant.
We shipped a best-effort CAS to work around this (conflict-surfacing writes that don't blind-retry + a conflict-aware claim + post-write ownership verification + a lease backstop). It collapses the double-claim race to near-zero and never silently clobbers, but it is explicitly not atomic: a residual window remains where two callers each run their post-write verification after both writes settle. A version/commit precondition on mutate would let us drop the post-write verification and make task_claim a real single-round-trip mutex.
Acceptance (from our side)
mutate accepts a manifest-version or commit-id precondition and atomically rejects the write if HEAD advanced.
- The rejection is distinguishable from other errors without string-matching a human message (distinct exit code or a stable error token in
--format json output), so a client can branch on "lost the CAS" vs. a real failure.
Happy to test against a pre-release build. Thanks!
Type: enhancement / feature request
Summary
Add a conditional-write (compare-and-swap) precondition to
mutateso a client can say "apply this write only if the graph/branch/table has not advanced past version N (or commit<id>), otherwise reject it". Today the OCC token needed for CAS is readable but not enforceable on write, so a single-round-trip store-level compare-and-swap is impossible from the client.Current surface (verified against the 0.8.x binary)
omnigraph commit list --json→ per-commitgraph_commit_id(ULID) + monotonicmanifest_version.omnigraph snapshot --json→ branchmanifest_versionand per-tabletable_version.omnigraph mutateaccepts no--if-version/--expected-commit/ precondition flag, and the query engine cannot express a compound... WHERE status = 'open'guard inside the mutation itself.stale view/manifest table version), but that only fires when two writes collide on the exact same base version — it is a race detector, not a value/precondition guard, and it cannot express "reject unless HEAD is still at the version I read."Net: a client cannot ask the store to "set this field only if the row/branch is still in the state I read," and have the store atomically reject a lost race.
Requested behavior
A precondition on
mutate(and ideallyload) that the engine checks at commit time and rejects if unmet. Any one of these shapes would unblock us; listed roughly in order of preference:Version/commit precondition (coarse, simplest):
Reject with a distinct, machine-detectable error (distinct exit code or a stable error token in the JSON body — not just a message substring) when the branch has advanced past
<N>/<id>.Per-table version precondition: same idea keyed on the
table_versionfromsnapshot(--if-table-version <table>=<v>), so an unrelated write to a different table doesn't spuriously fail the CAS.Conditional mutation guard (finest, most work): let the mutation itself carry a
WHERE-style predicate evaluated atomically at commit (SET status='in_progress' ... IF status='open'), so the precondition is on the row's value rather than a global version. This is the strongest form and removes the read-then-CAS round trip entirely.The coarse version-based form (1) is enough for our use case.
Use case / motivation
We use omnigraph as a shared, multi-writer work-coordination graph (an MCP "task tracker" backing parallel AI agents and multiple humans).
task_claimis the mutex: read a task, and if it is unclaimed, writestatus=in_progress, assignee, claimed_at.On a local
.omnistore an advisoryflockserializes writers, so this is safe. But in the deployed topology the store is a sharedomnigraph-serverover http(s)/S3, where the local lock cannot coordinate across pods. Two agents that both read a task asopenboth write their own claim; without a store-enforced precondition, last-write-wins silently clobbers the first claimant.We shipped a best-effort CAS to work around this (conflict-surfacing writes that don't blind-retry + a conflict-aware claim + post-write ownership verification + a lease backstop). It collapses the double-claim race to near-zero and never silently clobbers, but it is explicitly not atomic: a residual window remains where two callers each run their post-write verification after both writes settle. A version/commit precondition on
mutatewould let us drop the post-write verification and maketask_claima real single-round-trip mutex.Acceptance (from our side)
mutateaccepts a manifest-version or commit-id precondition and atomically rejects the write if HEAD advanced.--format jsonoutput), so a client can branch on "lost the CAS" vs. a real failure.Happy to test against a pre-release build. Thanks!