Accountable change-control for agent behavior.
Your agents already work. Can you prove what they changed — and undo it if it was wrong?
The problem
Traditional software governs behavior change through a whole discipline: version control, review, CI gates, deploys, audit logs. Every change is proposed, approved, recorded, and reversible.
Agents have none of that — yet their behavior mutates continuously:
- A tool contract changes underneath them. The carrier renames
order_idtotracking_ref. Your agent starts asking customers for a tracking number it was supposed to look up itself. - They loop. Two agents hand the same task back and forth until something runs out — usually your budget.
- They "learn" invisibly. Something works once, gets reused forever, and nobody approved it or can point at why.
- There is no paper trail. "Why did it do that?" has no answer that survives the chat scrollback.
The usual fix is a smarter prompt or a retry. That is expensive at runtime, non-deterministic, and unauditable.
What graxella does
It sits underneath your agents — you keep writing plain LangChain or LangGraph — and turns behavior change into something with a process:
tool breaks → repaired once → cited proposal → you approve → permanent rule
↓
evidence turns bad → auto-demoted
One rule explains the whole design: the LLM may propose; the evidence decides. Routing, promotion, demotion, and every verdict are deterministic and recorded. A model appears in exactly one place — the drift healer's proposal step — and even there its output is validated against the real fallback before it is ever trusted twice.
The code you actually write
import graxella
from pydantic import BaseModel
grx = graxella.Session("support-desk", domain="support")
class TrackRequest(BaseModel): # the carrier's NEW schema
tracking_ref: str # ...it used to be order_id
def carrier_v2(args: dict) -> str:
req = TrackRequest(**args) # a real, validating client
return f"parcel {req.tracking_ref}: out for delivery"
@grx.tool(fallback=carrier_v2) # <- the only graxella line
def track_shipment(order_id: str) -> str:
"""track a shipment's delivery status by order id"""
return carrier_v2({"order_id": order_id}) # drifts: the old field name
That's it. @grx.tool returns a real LangChain BaseTool, so it drops
into create_agent(llm, [track_shipment]) unchanged.
The first time the drift happens, graxella repairs it, caches the repair as a deterministic recipe, and files a cited proposal for you:
track_shipment.invoke({"order_id": "A-1042"})
# -> 'parcel A-1042: out for delivery' the customer never saw a failure
grx.healer_calls # 1 — repaired once, never again
grx.pending() # 1 — nothing was promoted silently
print(grx.why(grx.pending()[0])) # the cited reasoning behind the verdict
Approve it and it becomes a permanent rule. Later, if the evidence turns
against that rule, grx.reconcile() demotes it on its own — no human,
no LLM, just the posterior:
reconcile(): promoted=1 demoted=1
demoted apr_581870be...: status=rolled_back
reason='posterior 0.29 < 0.5 over 5 uses (1 ok / 4 failed)'
That last part — un-learning — is the piece most agent-memory systems don't have. Anything can accumulate rules. Removing one on evidence is what makes it change-control rather than a cache.
Install
pip install graxella # everything above works
pip install "graxella[heal]" # + the built-in drift healer (DSPy/Ollama)
One command, one distribution: the A2A mesh (agent2society) and the
memory engine ship inside the wheel, so there is no sibling package to
version-match. Extras are only for things you might
genuinely not want: [heal] (a local model runtime for repairing
ambiguous drift), [langgraph] (the graph runtime, for the mesh
adapters and tutorials 08+), [api] (the operator dashboard behind
grx.serve() and graxella show), [embed] (local
sentence-transformers), [otel], [mcp] — or [all].
Nothing calls out to a hosted service — the healer runs against a local Ollama by default, and without one, drift fails loudly rather than faking a repair.
What's new in 0.2
0.1 governed one agent's tools. 0.2 governs the organisation around the
agent, and ships as one wheel. In full, with the reproduction behind each
fix, in CHANGELOG.md; in one screen:
| One distribution | agent2society (the A2A mesh) now ships inside graxella. pip install graxella is the only install; no sibling to version-match. |
| Teams, consensus, self-awareness | Teams nest into teams; six conflict resolvers; agents carry declared and ledger-earned limitations and a live status board. § |
| A governed org chart | A panel that keeps agreeing becomes a gated proposal to collapse to one agent, filed by reconcile() unprompted, demoted the moment that agent fails. |
| Trust joined to routing | trust_weight lets the ledger's record overrule a better capability match; unavailable agents are removed from routing outright. § |
| Token economy | Usage from any gateway (Portkey, LiteLLM, raw SDKs), your own price book, cost ceilings that fire, and avoided() — model calls the ledger proves you no longer make. § |
graxella.demo() and /lens |
Fifteen acts on a real ledger, then the page that explains what graxella did — on the demo, or on your own system. § |
| Un-learning, concurrency, security, migrations | Rule-scoped health with a failure streak; single-flight healing and per-tenant WALs; an authenticated operator API that records the real principal; a versioned ledger schema migrated in place. |
| Pressure-tested | 20k-outcome ledgers, 16 concurrent routers with statuses flipping underneath, hostile 10k-character tasks, cross-process reads during writes — and the bugs those found, fixed. |
See it in one command
import graxella
graxella.demo()
or graxella demo. It builds a real ledger, runs a scripted incident
through every governed surface, and opens the operator UI on what it
produced. Fifteen acts:
| 1–3 | an observed tool, a vendor renaming a field, one healer call |
| 4–6 | evidence accumulating, a promotion, a rule un-learned |
| 7–9 | a routed mesh, a three-level team hierarchy, a disagreement escalated to a human |
| 10–11 | a collapse proposal filed by reconcile() unprompted, a bounded trajectory |
| 12 | the ledger overruling a better capability match |
| 13 | an unavailable agent taken out of routing, and brought back |
| 14 | a declared cost ceiling firing on a real dispatch |
| 15 | what the run spent, and the model calls it never made |
No model, no API key, no network: the healer, router and resolvers in the script are deterministic, so the story and the numbers are identical on every machine. What it prints:
healing 1 healer run(s), at most one model call each; 18 repairs applied with no model
rulebook 1 active, 1 un-learned
review queue 2 proposal(s) awaiting a human
routing 2 route(s) changed by evidence
spend 49,700 tokens over 11 metered call(s), $0.1445
avoided 17 model call(s) not made (~$0.0504)
Token reports in the script are staged in the OpenAI shape at illustrative
rates; everything computed from them runs through production code.
The UI needs pip install "graxella[api]".
The UI's /lens page answers the day-one question — what did graxella
do to my system, and why should I trust it in production? — with the
loop act by act, the repairs serving traffic, the repairs it took back
out with the evidence that convicted them, what is waiting on you,
and which agents are trusted versus what the ledger says they are bad at.
The same view on your own system
Nothing about those pages is demo-only. Open them on the ledger your agents actually write to, either from inside the running process or from outside over an existing workdir:
grx.serve(port=8321) # in-process: the full view, live teams included
graxella show --workdir .graxella/support-desk # any session's workdir
Both print the /lens and trust-center URLs with the operator token.
graxella show is honest about what an outside process cannot know: the
ledger-backed parts are exact, and the parts only a live process holds
(a team's shape right now, the status board) show as absent, never as
stale. Details, auth and the PostgreSQL case:
docs/OPERATOR_UI.md.
Teams, consensus, and a governed org chart
Routing one task to one agent is the easy case. When a decision needs several agents, graxella makes the shape of that group reviewable:
reviewers = grx.team("reviewers", [risk, legal], # a panel
pattern="consensus", resolver="unanimous")
pricing = grx.team("pricing", [analyst, reviewers]) # routed, over a panel
app = grx.mesh([triage, pricing]) # teams nest into meshes
- Hierarchy. A team is itself a member, so patterns compose and differ per level in the same run. Nothing above a team knows its shape.
- Consensus with a stated policy.
majority,trust_weighted(votes weighted by each member's cited record),specialist,unanimous,escalate. Every verdict names who dissented and what they said; a tie escalates instead of picking quietly. - Agents that know their limits. Peers see each other's
capabilities, the limitations the ledger earned for them (the error
classes they actually fail with), and a live
ready / busy / degraded / unavailablestatus. An unavailable member is never dispatched, and its absence is a recorded abstention, not a gap. - The org chart is governed too.
adaptive=Trueand a panel whose answer one member reproduces accrues evidence for collapsing to that member — through the same Evidence Gate, promoted byreconcile(), and demoted the moment that member starts failing. A demoted collapse is the panel coming back.
Three LLM calls become one when the evidence says the panel stopped buying anything, and three again when it stops being true.
Routing that knows who actually works
Your capability graph answers who matches this task. Your ledger answers who actually succeeds at it. graxella joins them:
app = grx.mesh([billing_v1, billing_v2], trust_weight=0.5)
app.route("refund this invoice")
print(app.last_routing_diff.render())
# trust routing picked billing_v2 over billing_v1: billing_v1 matched
# better (fit 0.68 vs 0.62) but its record here is trust 0.27 over 11
# call(s) against trust 0.89 over 42 call(s)
fitmultiplies, it never adds. Evidence re-orders the agents that already fit; a flawless record at something else can't win a task the agent doesn't match.- No model on this path. It's arithmetic over ledger rows plus a shortest path, so the same ledger routes the same way every time — which is the only reason a route can be replayed or audited.
- Failover is a shortest path. Edge cost
-log(p_success), so two 0.9 agents in sequence beat one 0.75 agent. trust_weight=0.0is the default and reproduces every earlier release exactly. graxella doesn't change dispatch behaviour silently.- Exploration is built in. Near-ties go to the agent with less evidence — otherwise trust routing is a rich-get-richer trap where one bad afternoon strands an agent forever.
grx.report("billing_v1", "unavailable") removes an agent from routing
entirely, at any weight. Capacity isn't quality, so it's removed rather
than down-weighted.
What it cost, and what it didn't
graxella routes no model calls and owns no tokenizer — your gateway (Portkey, LiteLLM, your own proxy) picks the model and reports the usage. What graxella adds is the accounting, and one number nobody else can produce:
grx = graxella.Session("desk", domain="support",
prices={"gpt-4o": (2.50, 10.00)}) # your rates, $/Mtok in, out
...
print(grx.spend().render())
# spend 1,500 tokens (1,000 in / 500 out) over 1 metered call(s) · $0.0075
print(grx.avoided().render())
# avoided 18 model call(s) not made (18 deterministic repair)
# ≈ 22,500 tokens ≈ $0.1125 — median metered call here is 1,250 tokens
Rates can also come from GRAXELLA_PRICES (a JSON map), so they stay out
of source control.
avoided() only counts calls the ledger proves used to happen. A
repair after the first qualifies — the one persisted transform proposal
is the receipt for the one healer call that was ever paid for.
Deterministic routing does not: a hand-written if/elif is free too, so
billing that against an LLM router you never wrote would be a fiction.
Two things it refuses to do, because both would be lies: it ships no
price list (an unpriced model costs None, never $0) and it ships no
tokenizer (token counts come from your provider's own usage report, the
only authority on what you were billed).
Measured, not asserted
Every number here comes from a script in this repo that you can run. The
runs are on small local models (qwen2.5:7b, nomic-embed-text).
| What | Result | Produced by |
|---|---|---|
| Routing across 15 paraphrased/slang tickets | 15/15 vs 13/15 for a hand-written keyword router | tutorial 11 §A |
| A runaway two-agent handoff loop | stopped at 3 hops + escalated, vs 20 hops burned by a hand-rolled loop that never detects it | tutorial 11 §B |
| Repairing a drifted tool | 1 healer call, ever — then a cached deterministic recipe | tutorial 02 |
| Test suite | 721 passed, 3 skipped (skips need a PostgreSQL URL) | uv run pytest |
| Load-bearing claims, checked in CI | 5 probes | benchmarks/eval_harness.py |
What these numbers are not: single-run results on one small domain, not a statistically powered benchmark. The CI scorecard exists so they fail loudly when they stop being true.
Learn it
tutorials/ is a graded path — 01–06 need no LLM at all:
| # | Tutorial | You learn |
|---|---|---|
| 01–03 | first tool → self-healing → review queue | a plain function becomes governed; a drift heals once; a human approves it into a permanent rule |
| 04–06 | mesh · recall · audit | multi-agent routing with no routing-LLM, memory that recalls what worked, "why did it do that?" in one call |
| 07–08 | LangChain · LangGraph | your real agents, unchanged, governed underneath |
| 09–10 | handoffs · supervisor team | typed A2A handoffs, loops caught and escalated, a full org chart |
| 11 | capstone notebook | every layer on one hierarchical org — then the same org rebuilt with zero graxella, compared on tokens, hops, and failure modes |
Honest limits
This project's whole claim is accountability, so the limits are stated rather than buried:
- It does not make your model smarter, and it does not claim a lower hallucination rate. Tutorial 11 contains a live probe where the governed agent hallucinated exactly as badly as the ungoverned one, and the built-in claim-detector missed it on both sides. That result is kept in the notebook. What differs is that the governed side's tool trail makes the false claim checkable afterwards.
- Governance is detection-only. graxella flags reasoning/action mismatches and constitution violations; it does not silently block or rewrite your agent's output.
0.2.x, alpha. The API surface is small and tested, but it will move.CHANGELOG.mdnames every defect each release fixed and the ones it knowingly leaves open.- The drift healer needs a local model (or your own
@grx.healer). Without one, drift fails loudly — it never fakes a repair.
Where it sits
Not a competitor to your agent framework — a layer under it.
| Guardrails.ai | NeMo Guardrails | LangGraph alone | graxella | |
|---|---|---|---|---|
| Validate a single output | ✅ | ✅ | — | ✅ |
| Repair a broken tool contract | — | — | — | ✅ |
| Evidence-gated promotion | — | — | — | ✅ |
| Reverse a learned behavior | — | — | — | ✅ |
| Cited audit trail per decision | — | — | — | ✅ |
Guardrails and NeMo answer "is this one output acceptable?". graxella answers "what changed in my agent's behavior, who approved it, and can I undo it?" — a different question, and they compose fine.
Docs
docs/FIRST_CUT_SCOPE.md— the problem, and what this first cut does and doesn't claimdocs/OPERATOR_UI.md—/lens, the trust center and the topology map on your own ledger:grx.serve(),graxella show, auth, PostgreSQLdocs/HEALING.md— the heal ladder, drift taxonomy, recipe capabilitiesdocs/specs/— the binding Promotion, Disclosure, Orchestration and Routing specsCONTRIBUTING.md— setup, and the honesty contract for changes
License
Apache-2.0 — see LICENSE.
Release files for graxella 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| graxella-0.2.0.tar.gz | 363.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| graxella-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 788.6 kB
Release files / graxella-0.2.0.tar.gz
| Download URL | graxella-0.2.0.tar.gz |
|---|---|
| Size | 363.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f8af3148fa8d058fbf4315d64089978c8dc07b22e0b22478259e940b0f746d9e
|
|
BLAKE2b-256 checksum How to use checksums |
2ecf6e1ea7b18e6ad50e2f853cf355e47bf9cb218271fa1a916e78b4cc647692
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / graxella-0.2.0-py3-none-any.whl
| Download URL | graxella-0.2.0-py3-none-any.whl |
|---|---|
| Size | 425.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
896578ebe89f2cf7eb9b428f773f2bf95cedb10c9fc2bc73df45f57c9a54f18c
|
|
BLAKE2b-256 checksum How to use checksums |
da11a5bec099d3653fd121b4058d2d3816570da00f0fc2e6cb188743cad144d1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency log