---
title: Agent-Safe Mutations
description: Plan, apply, and roll back data changes safely — a dry-run produces a reviewable token, apply commits in one transaction, and rollback restores the recorded before-state.
---

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 ships immediate Mongo-style updates ([`update-one`](/docs/cli/#update-one), [`update-many`](/docs/cli/#update-many), [`bulk-write`](/docs/cli/#bulk-write)) for code paths you trust — and for the changes an agent should not make blindly, it makes mutations *reviewable and reversible* by splitting them into three explicit steps: **plan**, **apply**, and **rollback**.

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](/docs/storage/) 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](/docs/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](/docs/query-operators/).

### 1. Plan a change (dry run)

```sh
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:

```json
{"token":"<token>","matched":3,"sampleBeforeAfter":[{"before":{...},"after":{...}}]}
```

- `token` — the opaque identifier you pass to `apply`.
- `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 `_id` is re-inserted into every `after` document after the update runs. No update operator can ever change a document's identifier.

### 2. Apply the plan

```sh
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:

```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

```sh
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:

```text
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.

```sh
# 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`](/docs/cli/) 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](/docs/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](/docs/mcp/). The `omgdb mcp` server advertises three matching tools:

| MCP tool | Arguments | Required scope |
| --- | --- | --- |
| `plan_update` | `path`, `collection`, `filter`, `update` (all required — pass `{}` as the filter to match all documents) | `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](/docs/mcp/) for scope details and the full tool catalog.

> **Tip:** A safe agent pattern is to run `plan_update`, surface the `matched` count and `sampleBeforeAfter` to a human (or a higher-trust reviewer), and only then call `apply` with the returned token — keeping `rollback` available if the applied change turns out to be wrong.
