How it works
Recovery — undo
When a run goes wrong, gmonk reverses it — per action, drift-checked against the live system, and recorded. It never overwrites a change it didn't make.
Preview, then apply
gmonk runs # find the run
gmonk reverse-plan <run-id> # read-only preview + live drift check
gmonk reverse-run <run-id> --dry-run # show exactly what an undo would do
gmonk reverse-run <run-id> # apply — only 'ready' steps run
gmonk reverse-run <run-id> --force # also overwrite drifted rows At apply time each action is classified live:
| Status | Meaning |
|---|---|
| ready | Before-state exists and the system is exactly as the agent left it → safe to undo. |
| drift | Something changed since the agent → undoing would clobber it → skipped unless --force. |
| noop | Already at the before-state — nothing to do. |
| blocked | No safe automatic undo (external/opaque, or nothing captured) → needs a human. |
Concurrency-preserving undo
The differentiator. Rather than writing an old snapshot back (last-writer-wins), gmonk recovers the operation and applies its inverse atomically, so concurrent writes survive. The op algebra covers:
- additive —
col = col ± n - multiplicative —
col = col × k(exact rational inverse) - jsonb-merge — per-key register; inverse restores or removes each key
- array-append — multiset; inverse removes the appended elements
Commutativity is verified, not assumed: if a known later write on the same cell doesn't commute, gmonk refuses to silently compose it and surfaces drift instead.
Causal excise
Undo one action and exactly its transitive causal dependents — from the dependency graph — all-or-nothing, leaving independent work untouched. This is the flagship surgical undo.
gmonk excise <run-id> <seq…> Rewind-repair-replay & point-in-time
- Rewind-repair-replay (
gmonk rrr) — rewind to a bad step, repair it (exclude/override), and replay later steps forward in dependency order, preserving good downstream work. - Point-in-time (
gmonk state-at) — reconstruct the value of any touched row as-of just before a given action. - Blast radius (
gmonk deps) — show the dependency graph and everything an action would affect.
External compensation
Effects that can't be restored by writing state back — a payment charge, a
created SaaS resource — are reversed by a compensating call you
define using the open-source @gmonk/sdk. gmonk builds
the descriptor (including an idempotency key) and hands it to your executor;
it never calls a third party on its own.
import { registerCompensation, registerCompensationExecutor } from "@gmonk/sdk";
registerCompensation({
tool: "stripe.createCharge",
capturesFromResult: ["charge_id"],
compensate: ({ charge_id }) => ({
tool: "stripe.refund",
args: { charge: charge_id },
idempotencyKey: `gmonk-refund-${charge_id}`,
}),
});
registerCompensationExecutor("stripe.refund", async (desc) => {
const refund = await stripe.refunds.create(desc.args, { idempotencyKey: desc.idempotencyKey });
return { ok: true, detail: `refunded ${desc.args.charge}`, result: { refund_id: refund.id } };
}); Point gmonk at it with GMONK_CONFIG=/path/to/gmonk.config.mjs.