A transactional resource runtime for AI agents: undo the reversible, gate the irreversible — against your backend's own state.
Project description
Pherix — nothing your agent does half-happens
A transactional resource runtime for AI agents: undo the reversible · gate the irreversible — against your backend's own state. Audit, replay, and isolation fall out for free.
Pherix is a Python library (+ TypeScript SDK) that wraps your agent's tool-call layer and gives database-style guarantees — atomicity, isolation, capability enforcement, durability — over the external side-effects of the tool calls (DB writes, file writes, API calls). It does not run your agent or call any LLM. You keep your existing agent loop and model provider; Pherix sits underneath at the tool-call layer. Every side-effecting call becomes an entry in one append-only journal — commit() folds it forward, rollback() folds it back.
What it is not: not durable execution (Temporal replays your code; Pherix transacts your resources), not observability (LangSmith/Langfuse watch; Pherix enforces and reverses), not an agent framework (it wraps the tool calls of an agent you already have).
How it works
flowchart TB
subgraph agent["Your agent — stays transaction-unaware"]
loop["agent loop<br/>calls @tool functions"]
end
loop -->|each side-effecting call| txn
subgraph pherix["Pherix — wraps the tool-call layer"]
txn["Transaction<br/><i>the journal: append-only, ordered Effects</i>"]
txn --> route{"reversible?"}
route -->|yes| rev["Reversible lane<br/>run live + snapshot"]
route -->|no| irr["Irreversible lane<br/>stage → gate / compensate"]
rev --> adapters
irr --> adapters
adapters["ResourceAdapters<br/><i>snapshot · apply · restore</i>"]
txn -. records .-> audit["AuditJournal<br/><i>the audit log</i>"]
end
adapters -->|apply / restore| resources[("real resources<br/>DB · filesystem · HTTP APIs")]
Every side-effecting tool call becomes an Effect appended to the journal. commit() folds the journal forward (apply each effect); rollback() folds it backward (restore the snapshot, or run the compensator). Reversible effects run live and undo via the backend's own savepoints; irreversible ones stage and only fire at commit — gating on approval if they can't be undone. Same engine, two directions.
Who it's for
Anyone shipping action-taking agents to production — where a tool call writes to a database, touches the filesystem, or fires an irreversible API request, and "the effect just happened" is not good enough.
Quickstart
Pre-release. The wrap below is the whole integration — no migration, no rewrite: declare your side-effecting tools, wrap the run.
Using a coding assistant? Point Claude Code / Cursor / Aider at
llms-full.txt— a complete, executable integration recipe (with the gotchas spelled out) written so an LLM can wrap your agent in Pherix correctly without you fighting the API.llms.txtis the curated index of all docs.
Install
pip install pherix
Or from source: git clone https://github.com/LukeyP02/Pherix && cd Pherix && pip install -e .
Run it — 30 seconds, no API key, no network. One reversible DB write that rolls back, one irreversible send that gates at commit:
python examples/quickstart.py
=== rollback ===
inside txn: ['ada']
after rollback: [] # the write was undone — nothing persisted
=== gate (not approved) ===
commit blocked: ... staged irreversible effects need approve_irreversible(): <effect-id>
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 40-line source is the minimal real wrap; the rest of this section walks through each piece.
1 — Declare your tools with @tool. Mark each side-effecting function with the resource it touches. The agent body that calls them stays transaction-unaware — just a plain loop.
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)
2 — Wrap the run in agent_txn(...). Pass your adapters. Reversible effects journal live and roll back on demand; leaving the block cleanly commits them.
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.
3 — Irreversible effects gate. Declare reversible=False. Add a compensator (a semantic inverse) if one exists; otherwise the effect blocks at commit until explicitly approved.
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
stripe.email(to, body)
adapters = {"sql": SQLiteAdapter(conn), "http": HTTPAdapter()}
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.
Reversible effects run live and roll back via the backend's own savepoints. Irreversible ones are staged — they only actually happen at commit, and an un-compensable one gates on explicit approval. Calling rollback() before commit means the irreversible effect simply never happened. That's the whole point.
What you get
- Undo the reversible — DB and file writes roll back via the backend's own savepoints.
- Gate the irreversible — un-undoable effects stage and block at commit until approved.
- Audit everything — every effect, its arguments, and its outcome lands in the journal; the journal is the audit log.
See it / explore
python -m examples.demo # the narrated 3-act demo, governed vs ungoverned
Three acts, side by side, deterministic and offline: a WHERE-less DELETE contained by rollback; a $480k wire gated before it can fire; and the audit journal both runs wrote, read back. It ends by pointing you at the journal it produced:
python -m pherix.inspector.seed demo.db # generate a representative audit journal
python -m pherix.inspector --db demo.db # open the read-only audit console over it
Runs fully offline — no API key, no model. The seeded journal shows every story the console renders: a clean commit, a rollback, a gated irreversible, a STUCK transaction, and a dry-run.
Learn more
site/docs.html— how it works, end to end.- The rest of the static site (
site/index.html,site/get-started.html,site/demos.html) is served bypython -m http.serverfrom the repo root.
Status
Pre-release. The engine is built — both lanes, MVCC isolation, replay, dry-run, crash recovery, policy, the MCP gateway, the flight recorder; 16 Python / 14 TS adapters; ~940 Python + 210 TS tests, fully offline. Open-core: the library and the read-only audit console are MIT and free forever; a hosted control plane (the journal as a fleet-wide system of record) is the eventual paid layer — nothing you run on one machine is ever paywalled. Source: github.com/LukeyP02/Pherix.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pherix-0.1.0.tar.gz.
File metadata
- Download URL: pherix-0.1.0.tar.gz
- Upload date:
- Size: 140.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4744b131ae83324118b2aab61ac48f6c1d3eb14a467e102b0940bedbb946b933
|
|
| MD5 |
279660141487cf8a6aa2ddc794dcb0c3
|
|
| BLAKE2b-256 |
2f4bd31e34e32028b44106b677548def203b3c53c3957414bc1aed1c9964075a
|
File details
Details for the file pherix-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pherix-0.1.0-py3-none-any.whl
- Upload date:
- Size: 178.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f8660fc394b04ee660a4673fc7c6e8f1e72dc0f7a9bd5273d2c80e31f01fadb5
|
|
| MD5 |
49acba4d93095a6fc1bb73ac2b024ad1
|
|
| BLAKE2b-256 |
5f19f71de6b00a7a54e8fe554e31d57150b15c8abfbcf2848ebb47e9d9167e9d
|