Transactions for AI agents.
AgentTx wraps every tool call in an ACID transaction. When a step fails, it finds the step that really caused it, rewinds state in milliseconds, undoes side effects and hands your agent a one-line fix.
cargo run --release -p agenttx-protocolThe problem
Agents fail in ways retries can’t fix.
Tool-using agents change real systems. Without transactions, every failure leaves a mess — and a bigger prompt.
Silent root causes
Step 3 stores the wrong id. Step 9 fails on a foreign key. Retrying step 9 forever never fixes step 3.
Poisoned context
Every failure pastes kilobytes of stack trace into the prompt, until the model reasons about noise.
Half-done side effects
Restarting from scratch re-sends emails, leaves draft files behind and trips over rows it already wrote.
How it works
One failed step. Four deterministic moves.
Everything below runs inside the proxy in microseconds to milliseconds, before your agent sees a response.
- 01
Clean the error
A compiled RegexSet turns any stack trace into one actionable line. No LLM call.
PSQLException: ERROR: insert or update…Hint: Foreign key failed for 'customer_id' - 02
Trace the root cause
A dependency graph links step outputs to later inputs and walks the key back to its producer.
step 6 · customer_id← step 2 · session.value - 03
Rewind everything
Undo-journal snapshots restore state atomically, Saga actions undo files and APIs, staged emails are dropped.
restore_snapshot(@2)undo draft.json · drop 1 email - 04
Resume, bounded
The agent resumes at the right step with the hint as a constraint. A replay budget guarantees termination.
ROLLBACK_TRIGGEREDnext_step_id = 2
Benchmarks
Before and after, measured.
Same scripted agent, same tools, with and without AgentTx. Every number below comes from the committed benchmark run — reproducible with one command.
Tool calls until the task succeeds — silent root cause
A wrong value from an early step fails a later step. Across all sizes: 222 calls without AgentTx, 184 with it.
- Without AgentTx
- With AgentTx
AMD Ryzen 5 5600H with Radeon Graphics · 12 cores · release build · Sep 15, 2026, 05:16 PM UTC
Features
Production primitives, not a prompt trick.
The guarantees come from the storage engine and the protocol, so they hold no matter which model or framework drives the agent.
ACID overlay
Private per-transaction writes, atomic commit and first-committer-wins conflict detection.
Learn moreMillisecond rewinds
An undo journal makes snapshots O(1) and restores proportional to changes, not database size.
Learn moreDependency graph
Explicit ${steps.N} references and value matching find the step that really broke things.
Learn moreSaga compensation
Crash-recoverable undo actions for files and APIs; emails and webhooks wait until commit.
Learn moreClean Hints
About 30 deterministic rules for SQL, Python, Java, Node, HTTP and more, with a smart fallback.
Learn moreBounded by design
Local backtracks, jumps and resets share an O(N log N) replay budget. Loops always end.
Learn moregRPC native
Language-agnostic protobuf contract, health checks and graceful shutdown. Or embed the Rust library.
Learn moreEmbedded RocksDB
No external database to run. Statically linked, WAL-durable, with optional fsync per write.
Learn moreIntegration
A dozen lines in the loop you already have.
Send tool calls through ExecuteStep, always resume at next_step_id, and put the returned constraints in your prompt. Works from any language with gRPC.
r = client.ExecuteStep(pb.ExecuteStepRequest(
transaction_id=tx, step_id=next_step,
tool_name=call.tool, arguments_json=json.dumps(call.args),
))
if r.status == pb.STEP_STATUS_ROLLBACK_TRIGGERED:
agent.rewind(r.rollback_to_step, reset_context=r.context_reset)
system_prompt.constraints = list(r.constraints) # one-line Clean Hints
next_step = r.next_step_idconst r = await executeStep({
transaction_id,
step_id: nextStep,
tool_name: "record.insert",
arguments_json: JSON.stringify({ table: "invoices", fields: { customer_id: "${steps.2.value}" } }),
});
if (r.status === "STEP_STATUS_ROLLBACK_TRIGGERED") {
console.log(r.strategy, r.clean_hint); // DEPENDENCY_JUMP, "Hint: Foreign key constraint failed…"
}
nextStep = r.next_step_id;let tx = engine.begin("billing-agent", Default::default(), None).await?.tx_id;
let outcome = engine.execute_step(StepRequest {
tx_id: tx.clone(),
step_id: 1,
tool_name: "fs.write".into(),
arguments_json: r#"{"path":"drafts/inv-1.json","contents":"{}"}"#.into(),
raw_context: String::new(),
}).await?;
engine.commit(&tx).await?; // publishes state, then dispatches staged emailsgrpcurl -plaintext -d '{"agent_id":"quickstart"}' \
127.0.0.1:50051 agenttx.v1.AgentTxService/BeginTransaction
grpcurl -plaintext -d @ 127.0.0.1:50051 agenttx.v1.AgentTxService/ExecuteStep <<EOF
{ "transaction_id": "$TX", "step_id": 1, "tool_name": "kv.put",
"arguments_json": "{\"key\":\"customer\",\"value\":\"c-1\"}" }
EOFArchitecture
A single Rust process between your agent and its tools.
Run it as a gRPC proxy next to any framework, or embed the engine as a library. State lives in embedded RocksDB; per-transaction mutexes keep steps ordered while transactions run in parallel. Read the architecture guide.
LangGraph, custom loops, MCP hosts — any gRPC client.
Databases, files, APIs. Emails via the outbox.
RegexSet → Clean Hint
jump · backtrack · reset
petgraph DAG
undo + staging queue
overlay state · undo journal · Saga logs — three column families
Open source
Built in the open, on GitHub.
The docs you are reading, the benchmark results and these numbers are loaded straight from the repository and refreshed on every push.
- Stars
- 1
- Forks
- 0
- Open issues
- 1
- Contributors
- 1
- Rust 53.9%
- TypeScript 41.7%
- CSS 1.7%
- Other 2.6%
Give your agents a safety net.
Five minutes to your first rollback. Apache-2.0, self-hosted, no telemetry.