Documentation

Contain the blast radius.

Wrap your agent's tool calls so the reversible ones roll back and the irreversible ones wait for your yes.

Pherix is a Python library (with a TypeScript SDK) that gives database-style guarantees — atomicity, isolation, capability enforcement, durability — over the external side-effects of an agent's tool calls. It doesn't run your agent or call an LLM: you keep your loop and model provider, and Pherix sits underneath at the tool-call layer. Zero dependencies — the kernel imports nothing; each adapter lazy-imports its own driver.

§ 01Install

Python 3.12+. The kernel is dependency-free.

shell bash
pip install pherix

Each adapter pulls only its own driver when you need it — e.g. pip install pherix[postgres]. From source: git clone https://github.com/LukeyP02/Pherix && cd Pherix && pip install -e .

§ 02Quickstart

Thirty seconds, no API key, no network — one reversible DB write that rolls back, one irreversible send that gates at commit:

shell bash
python examples/quickstart.py
output stdout
=== rollback ===
  inside txn:       ['ada']
  after rollback:   []            # the write was undone — nothing persisted

=== gate (not approved) ===
  commit blocked:   ... staged irreversible effects need approve_irreversible()
  emails sent:      []            # the un-undoable send never fired
  users persisted:  []            # and the DB write rolled back with it

=== gate (approved) ===
  emails sent:      [('grace@example.com', 'welcome')]
  users persisted:  ['grace']     # approved → both go through

That's the whole idea in one run: the reversible effect is undone exactly, the irreversible one is held until a human says yes. The rest of this page walks through each piece.

§ 03How it works

Every side-effecting tool call becomes an Effect appended to one append-only, ordered journal held by a Transaction. A per-resource adapter makes each entry executable and reversible — snapshot → apply → restore. Every capability is then just a traversal of that one log:

Two lanes come off the same engine:

Reversible

DB and file writes run live and undo via the backend's own semantics — a real SQL SAVEPOINT / ROLLBACK TO SAVEPOINT, filesystem copy-on-write. Exact, not best-effort.

Irreversible

A charge or an email can't be snapshotted, so it stages — it only fires at commit, and with no semantic inverse it gates: commit blocks until a human approves.

§ 04Declare your tools

Mark each side-effecting function with @tool and the resource it touches. The agent body that calls them stays transaction-unaware — just a plain loop.

tools.py python
import sqlite3
from pherix import AuditJournal, SQLiteAdapter, agent_txn, tool

@tool(resource="sql")
def insert_user(conn, name, role):
    conn.execute("INSERT INTO users (name, role) VALUES (?, ?)", (name, role))
    return name

def my_agent(team):
    # a plain agent loop — never transaction-aware
    for name, role in team:
        insert_user(name=name, role=role)

The connection is injected as the first argument by the adapter; the agent never sees it. resource="sql" routes the effect to the SQLite adapter you pass in next.

§ 05Run a transaction

Wrap the run in agent_txn(...) with your adapters. Reversible effects journal live and roll back on demand; leaving the block cleanly commits them.

run.py python
conn = sqlite3.connect("app.db", isolation_level=None)
audit = AuditJournal.in_memory()
adapters = {"sql": SQLiteAdapter(conn)}

with agent_txn(adapters, audit=audit) as txn:
    my_agent([("ada", "engineer"), ("grace", "scientist")])
    # caught a problem? roll the whole step back — nothing persisted:
    # txn.rollback()
# left the block cleanly → commit. The writes are now durable.

§ 06Gate the irreversible

Declare reversible=False. Add a compensator (a semantic inverse) if one exists; otherwise the effect blocks at commit until explicitly approved.

gate.py python
from pherix import HTTPAdapter, GateBlocked

@tool(resource="http", reversible=False, injects_handle=False)
def refund_charge(customer, amount):           # the semantic inverse
    stripe.refund(customer, amount)

@tool(resource="http", reversible=False, injects_handle=False,
      compensator="refund_charge")
def charge_card(customer, amount):             # auto-commits; refunded on rollback
    return stripe.charge(customer, amount)

@tool(resource="http", reversible=False, injects_handle=False)
def send_email(to, body):                      # no inverse — an email can't be un-sent
    mailer.send(to, body)

with agent_txn(adapters, audit=audit) as txn:
    charge_card(customer="alice", amount=4200)
    receipt = send_email(to="alice@example.com", body="receipt")
    # send_email has no compensator → commit BLOCKS at the gate until a
    # human (or a higher-trust policy) approves the un-undoable effect:
    txn.approve_irreversible(receipt.effect_id)
# no approval → GateBlocked is raised and the staged effects never fire.
The boundary is explicit. Whether an effect can be rolled back is the adapter's supports_rollback() flag, not a guess. An HTTP POST adapter returns False, so the runtime forces that effect down the staged/gated path instead of pretending it can reverse it.

§ 07Adapters

An adapter teaches Pherix how to snapshot, apply, and restore one class of resource. Sixteen ship in Python, fourteen mirrored in TypeScript:

Each lazy-imports its driver, so import pherix needs none of them. Pull a driver only to instantiate the matching adapter: pip install pherix[postgres], pherix[s3], pherix[redis], … or pherix[all-adapters] for the lot.

§ 08The audit journal

You don't need a disaster to get value on day one. The append-only journal is a flight recorder for what your agent actually did — what ran, what was held, what was undone, and why. Point the read-only console at a run:

shell bash
python -m pherix.inspector.seed demo.db   # a representative journal
python -m pherix.inspector --db demo.db   # the read-only audit console

§ 09What it's not

For the complete, executable integration recipe — written so a coding assistant can wrap your agent correctly — see llms-full.txt. Source and issues: github.com/LukeyP02/Pherix.