Metadata-Version: 2.4
Name: graxella
Version: 0.2.0
Summary: Accountable change-control for agent behavior: repair drift once, gate every promotion on evidence, un-learn what stops working.
Author: Graxella contributors
License: Apache-2.0
Project-URL: Homepage, https://github.com/graxella-ai/graxella
Project-URL: Source, https://github.com/graxella-ai/graxella
Project-URL: Issues, https://github.com/graxella-ai/graxella/issues
Project-URL: Tutorials, https://github.com/graxella-ai/graxella/tree/main/tutorials
Keywords: agents,langgraph,langchain,mcp,a2a,multi-agent,self-healing,datalog,provenance,audit
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic>=2.7
Requires-Dist: jsonschema>=4
Requires-Dist: pydantic-settings>=2.2
Requires-Dist: sqlmodel>=0.0.16
Requires-Dist: sqlalchemy>=2
Requires-Dist: langchain-core>=0.3
Provides-Extra: langgraph
Requires-Dist: langgraph>=0.2; extra == "langgraph"
Provides-Extra: ollama
Requires-Dist: langchain-ollama>=0.2; extra == "ollama"
Provides-Extra: mcp
Requires-Dist: mcp>=0.9; extra == "mcp"
Provides-Extra: otel
Requires-Dist: opentelemetry-api>=1.25; extra == "otel"
Provides-Extra: api
Requires-Dist: fastapi>=0.110; extra == "api"
Requires-Dist: uvicorn>=0.29; extra == "api"
Provides-Extra: embed
Requires-Dist: sentence-transformers>=3.0; extra == "embed"
Requires-Dist: sqlite-vec>=0.1; extra == "embed"
Provides-Extra: heal
Requires-Dist: dspy>=3; extra == "heal"
Requires-Dist: ollama>=0.3; extra == "heal"
Provides-Extra: examples
Requires-Dist: graxella[langgraph,ollama]; extra == "examples"
Provides-Extra: dev
Requires-Dist: graxella[api,langgraph,mcp,ollama]; extra == "dev"
Requires-Dist: pytest>=7; extra == "dev"
Provides-Extra: all
Requires-Dist: graxella[api,heal,langgraph,mcp,ollama,otel]; extra == "all"
Dynamic: license-file

<!-- AUTO-COPIED from the repo-root README.md by scripts/sync_readme.py -- do not edit this file directly, edit the root one and re-run that script. scripts/check_readme_sync.py enforces this in CI. -->

<div align="center">

<img src="assets/logo.svg" alt="graxella" width="380">

**Accountable change-control for agent behavior.**

Your agents already work. Can you prove what they changed — and undo it if it was wrong?

[![ci](https://github.com/graxella-ai/graxella/actions/workflows/ci.yml/badge.svg)](https://github.com/graxella-ai/graxella/actions/workflows/ci.yml)
[![python](https://img.shields.io/badge/python-3.12%20%7C%203.13-blue)](https://www.python.org)
[![license](https://img.shields.io/badge/license-Apache--2.0-green)](LICENSE)

</div>

---

## 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_id` to `tracking_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

```python
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:

```python
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

```bash
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`](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. [§](#teams-consensus-and-a-governed-org-chart) |
| **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. [§](#routing-that-knows-who-actually-works) |
| **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. [§](#what-it-cost-and-what-it-didnt) |
| **`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. [§](#see-it-in-one-command) |
| **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

```python
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:

```python
grx.serve(port=8321)          # in-process: the full view, live teams included
```

```bash
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`](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:

```python
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 /
  unavailable` status. An unavailable member is never dispatched, and its
  absence is a recorded abstention, not a gap.
- **The org chart is governed too.** `adaptive=True` and a panel whose
  answer one member reproduces accrues evidence for collapsing to that
  member — through the same Evidence Gate, promoted by `reconcile()`,
  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:

```python
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)
```

- **`fit` multiplies, 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.0` is 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:

```python
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](tutorials/11_capstone_governed_org.ipynb) |
| 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](tutorials/11_capstone_governed_org.ipynb) |
| Repairing a drifted tool | **1** healer call, ever — then a cached deterministic recipe | [tutorial 02](tutorials/02_self_healing.py) |
| 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`](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/`](tutorials/) is a graded path — 01–06 need **no LLM at all**:

| # | Tutorial | You learn |
|---|---|---|
| 01–03 | [first tool](tutorials/01_first_tool.py) → [self-healing](tutorials/02_self_healing.py) → [review queue](tutorials/03_review_queue.py) | a plain function becomes governed; a drift heals once; a human approves it into a permanent rule |
| 04–06 | [mesh](tutorials/04_agent_mesh.py) · [recall](tutorials/05_memory_recall.py) · [audit](tutorials/06_audit_trail.py) | multi-agent routing with no routing-LLM, memory that recalls what worked, "why did it do that?" in one call |
| 07–08 | [LangChain](tutorials/07_langchain_agent.py) · [LangGraph](tutorials/08_langgraph_mesh.py) | your **real** agents, unchanged, governed underneath |
| 09–10 | [handoffs](tutorials/09_agent_handoff.py) · [supervisor team](tutorials/10_supervisor_team.py) | typed A2A handoffs, loops caught and escalated, a full org chart |
| **11** | **[capstone notebook](tutorials/11_capstone_governed_org.ipynb)** | **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.md`](CHANGELOG.md) names 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`](docs/FIRST_CUT_SCOPE.md) — the problem, and what this first cut does and doesn't claim
- [`docs/OPERATOR_UI.md`](docs/OPERATOR_UI.md) — `/lens`, the trust center and the topology map on your own ledger: `grx.serve()`, `graxella show`, auth, PostgreSQL
- [`docs/HEALING.md`](docs/HEALING.md) — the heal ladder, drift taxonomy, recipe capabilities
- [`docs/specs/`](docs/specs/) — the binding Promotion, Disclosure, Orchestration and Routing specs
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — setup, and the honesty contract for changes

## License

Apache-2.0 — see [LICENSE](LICENSE).
