Letting an autonomous agent mutate a database directly is risky: a bad filter or a wrong operator can rewrite documents with no easy way back. OMGDB makes mutations reviewable and reversible by splitting a change into three explicit steps — plan, apply, and rollback — instead of mutating blindly.
A plan is a dry run that writes nothing: it computes which documents would change and a before/after sample, then persists a legible plan file keyed by an opaque token. A human or a supervising agent inspects that plan, applies it by token in a single transaction, and can later restore the recorded before-state. Because the op-log already records every operation, this audit/undo layer is a thin, natural addition rather than a bolt-on.
The workflow
The three steps map to three CLI subcommands and three MCP tools. Each plan is identified by a single opaque token (an ObjectId hex string) that is produced by plan-update and reused unchanged as the change id by apply.
| Step | Command | Writes data? | Reversible |
|---|---|---|---|
| Plan | omgdb plan-update <path> <collection> <filter> <update> | No (dry run) | n/a |
| Apply | omgdb apply <path> <token> | Yes, one transaction | Yes, via rollback |
| Rollback | omgdb rollback <path> <change_id> | Yes, one transaction | Restores recorded before-state |
The update document uses the same update operators as everywhere else in OMGDB — $set, $unset, $inc, $mul, $min, $max, $rename, and the array operators $push/$addToSet/$pull/$pop. The filter uses the standard query operators.
1. Plan a change (dry run)
omgdb plan-update app.omgdb <collection> '<filter>' '<update>'
plan-update compiles the filter, scans the collection, and for each matching document computes the resulting after document without writing anything to the store. It persists the plan as one NDJSON record per matched document under app.omgdb/pending/<token>.ndjson, then prints a summary as canonical JSON:
{"token":"<token>","matched":3,"sampleBeforeAfter":[{"before":{...},"after":{...}}]}
token— the opaque identifier you pass toapply.matched— the full count of documents the change would affect.sampleBeforeAfter— up to three{before, after}pairs so a reviewer can see the exact effect, even when more documents match.
The plan file itself is plain NDJSON you can cat: each line is a canonical-JSON object {ns, id, before, after}, holding both the original and the proposed document.
Note: During planning, the original
_idis re-inserted into everyafterdocument after the update runs. No update operator can ever change a document’s identifier.
2. Apply the plan
omgdb apply app.omgdb <token>
apply reads app.omgdb/pending/<token>.ndjson and replaces every recorded document inside a single store transaction (replace_one per record). The operation is all-or-nothing: if any replacement fails, none are committed. On success the plan file is moved from pending/ to app.omgdb/changes/<token>.ndjson, where it becomes a cat-able audit-and-undo record (it holds both before and after for every affected document). The command prints the result as canonical JSON:
{"changeId":"<token>","applied":3}
changeId is the same value as the original token — they are not distinct identifiers. An unknown token fails with a not-found error.
3. Roll back an applied change
omgdb rollback app.omgdb <change_id>
rollback reads app.omgdb/changes/<change_id>.ndjson and, in a single transaction, restores each document’s recorded before state. It prints the number of documents restored:
rolled back 3 document(s)
Note: Rollback leaves the change file in place and is not itself recorded as a new change. Re-running it simply restores the same before-state again; there is no forward “redo” of a rolled-back change.
Worked example
Insert a document, plan a change, apply it, then roll it back. This is the end-to-end flow exercised by the engine’s plan_apply_rollback_round_trip test.
# Create a store and insert a document.
omgdb create app.omgdb
omgdb insert app.omgdb u '{"name":"ada","age":30}'
# 1. Plan: set role=admin and increment age — nothing is written yet.
omgdb plan-update app.omgdb u '{"name":"ada"}' '{"$set":{"role":"admin"},"$inc":{"age":1}}'
# -> {"token":"<token>","matched":1,"sampleBeforeAfter":[
# {"before":{"_id":...,"name":"ada","age":30},
# "after":{"_id":...,"name":"ada","age":31,"role":"admin"}}]}
# The store is still unchanged: age is 30 and role is absent.
omgdb get app.omgdb u '{"$oid":"..."}'
# 2. Apply the plan by its token, in one transaction.
omgdb apply app.omgdb <token>
# -> {"changeId":"<token>","applied":1}
# Now age == 31 and role == "admin".
omgdb get app.omgdb u '{"$oid":"..."}'
# 3. Roll back by the change id (same value as the token).
omgdb rollback app.omgdb <token>
# -> rolled back 1 document(s)
# Restored: age == 30, role absent again.
omgdb get app.omgdb u '{"$oid":"..."}'
Audit trail
Applied changes accumulate as NDJSON files under app.omgdb/changes/, one per change id. Each file records both the before and after document for every row the change touched, so the directory is a legible, append-only audit log you can inspect with ordinary tools. These sidecar directories (pending/ and changes/) are bundled into single-file archives by omgdb pack alongside the op-log, so a packed store carries its pending plans and undo history with it.
This layer builds on the op-log, which already records every committed operation as the storage durability foundation — the plan/apply/rollback files add the before/after capture needed for human-reviewable, undoable agent edits.
MCP equivalents
The same workflow is exposed to agents over the Model Context Protocol. The omgdb mcp server advertises three matching tools:
| MCP tool | Arguments | Required scope |
|---|---|---|
plan_update | path, collection, update, optional filter | read-write |
apply | path, token | read-write |
rollback | path, change_id | read-write |
All three require read-write scope. A server started with --scope read does not advertise them in tools/list and refuses them at call time, so an untrusted agent can be handed a database it is structurally unable to mutate. plan_update returns the same {token, matched, sampleBeforeAfter} summary as the CLI and writes no data; apply commits a planned change by token; rollback reverses an applied change by id. See MCP integration for scope details and the full tool catalog.
Tip: A safe agent pattern is to run
plan_update, surface thematchedcount andsampleBeforeAfterto a human (or a higher-trust reviewer), and only then callapplywith the returned token — keepingrollbackavailable if the applied change turns out to be wrong.