Auditable agents for Python. Build knowledge assistants over docs, spreadsheets and the web — where the path per question isn't a graph you can draw. Every answer is computed, versioned and reproducible — not a string you have to trust.
Is: a Python library (3 core deps) · single-process · typed, versioned artifacts with provenance · deterministic replay · runs offline, no API key Isn't: a managed platform · a distributed task queue · a pre-built agent/tool marketplace
A question like "why did infra costs jump in Q2?" needs a spreadsheet, a policy doc and a page off the web — and the next question needs a different subset. That is not one graph you can draw up front. You declare what artifacts exist and what agents can do with them; the runtime derives the order from state.
Every derived artifact then links to what produced it
(Answer —supported_by→ Evidence —extracted_from→ Doc), so "why did it say
that?" is a query, not a guess — and the run is reproducible (context_hash)
and verifiable (reactifact replay --verify).
On top: a provable answer
examples/fintech_audit — a transactions CSV, a budget
CSV and a policy doc, no API key. The model never produces the number; plain
Python does, and the answer is linked to its evidence:
$ .venv/bin/python -m examples.fintech_audit.main
cloud spend: $45,000 (2026-04 $12k, 2026-05 $15k, 2026-06 $18k)
variance vs budget: +12.5% budget $40,000, policy threshold 10% → over
answer: Q2 cloud spend was $45,000 against a $40,000 budget (+12.5%) —
exceeds the 10% policy threshold. CFO approval is required.
citations: budget.csv, transactions.csv, policy.md
audit: Answer —supported_by→ {Variance, Spend, Table, Policy}
answer sha256 5461290d… · context sha256 24449f6f…
>>> re-running the pipeline hashes identically — or verify a saved run:
>>> reactifact replay <store> --session <id> --verify 24449f6f…
That hash is the whole point: the answer is reproducible and its provenance is
a queryable graph (context.related(answer.id, "supported_by")), not a log line.
The number is computed, and its provenance recorded, in plain Python — the model
is never the source of truth (trimmed from
examples/fintech_audit/produce.py):
@produce(Variance, reacts_to=Spend) # wakes when a Spend artifact exists
async def compute_variance(call: ProduceCall) -> None:
spend = call.trigger # the artifact that triggered this run
budget = cloud_budget(call.context) # read from budget.csv
pct = (spend.data.total - budget) / budget # deterministic — never the model
variance = call.effects.create_once_from(
spend, # stable id → idempotent re-runs (§42)
Variance(actual=spend.data.total, budget=budget, pct=pct,
threshold=0.10, within_policy=abs(pct) <= 0.10),
)
variance.link("calculated_from", spend) # provenance edge, queryable
answer = ctx.latest(AuditAnswer)
print(report_to_markdown(build_report(ctx, answer))) # hash per artifact + edges
print(context_hash(ctx)) # reproducible fingerprint
Knowledge assistants, live
The same shape over many sources, not a fixed path — the
knowledge CLI answers a harder, multi-source question
(docs + a CSV) with a real computed number and its sources, no LLM key required:
Two agents explore independently on their own forks and merge back automatically — and when they disagree, reactifact refuses to merge silently:
If you know Celery… — the programming model, not its distributed runtime
Python developers already know this model from Celery: define a task, declare what triggers it, let the runtime run it. reactifact applies it to agents — a task reacts to a typed, versioned artifact appearing in the context, not to a queue message you push or a graph edge you draw. The runtime derives what runs next from state.
| Celery | reactifact |
|---|---|
| a task | @produce(Model) — a unit of work that writes an artifact |
delay() / apply_async() |
you don't call it: creating the input artifact is the trigger |
| routing key / queue | Consume(Type) — which artifact type wakes the task |
| chain / group / chord | several consumes / produces; the runtime derives the order |
| result backend | the Context — typed, versioned artifacts |
| worker | Runtime |
Not inherited: a broker, a worker pool, acks_late/redelivery, cross-process
exactly-once. reactifact is a library with an in-process runtime — the model is
Celery-shaped, the deployment is not. (Session-backed runs do survive a restart;
see Durability & resume.)
pip install reactifact
Runs offline, no API key needed — paste this straight into a .py file. Two
agents, no graph edge declared between them — the second reacts because the
first one's output exists, and the answer carries proof of where it came
from:
from pydantic import BaseModel
from reactifact import Budget, Consume, Context, Runtime, RuntimeResources, create_agent, produce
class Question(BaseModel):
text: str
class Evidence(BaseModel):
text: str
class Answer(BaseModel):
text: str
DOCS = {
"refund": "Refunds are available within 14 days of purchase.",
"pricing": "The Pro plan is $49/month, billed annually.",
}
@produce(Evidence)
async def find_evidence(call):
question = next((a for a in call.inputs if isinstance(a.data, Question)), None)
if question is None:
return None
hit = next((v for k, v in DOCS.items() if k in question.data.text.lower()), None)
if hit is not None:
call.effects.create(Evidence(text=hit))
@produce(Answer)
async def answer_from_evidence(call):
evidence = next((a for a in call.inputs if isinstance(a.data, Evidence)), None)
if evidence is None:
return None
call.effects.create(Answer(text=evidence.data.text)).link("supported_by", evidence)
search_agent = create_agent("search", consumes=[Consume(Question)], produces=[find_evidence])
answer_agent = create_agent("answer", consumes=[Consume(Evidence)], produces=[answer_from_evidence])
ctx = Context(resources=RuntimeResources())
runtime = Runtime(ctx, agents=[search_agent, answer_agent], budget=Budget(max_runs=10))
ctx.create(Question(text="what's your refund policy?"))
runtime.run() # search_agent and answer_agent both react — nobody wired them together
answer = ctx.latest(Answer)
evidence = ctx.related(answer.id, "supported_by")[0]
print(answer.data.text) # "Refunds are available within 14 days of purchase."
print("supported_by:", evidence.data.text) # provenance you can trace, not just a string in a log
How it works
ARTIFACT CREATED / UPDATED
│
▼
AGENTS REACT ──self.effects──► Effects ──compile──► Patch
▲ │
└──────────────────────────────────────────────────────┘
Context v+1
A produce writes what should change (self.effects.create/update/link/ask) and
returns None; the runtime compiles the effect set into one atomic Patch
and moves the context to the next version. The event that wakes an agent is
derived from that same change — the causal chain can never drift from the
actual state.
What makes it different
| Traditional agent (LangGraph / CrewAI / LangChain) | reactifact |
|---|---|
| A program follows a graph / plan | Agents react to state changes |
| Messages are strings | Typed, versioned artifacts (Claim, Evidence, Answer) |
| Orchestration is explicit wiring | Orchestration falls out of the state |
| A unit of work returns a change | An agent writes effects; the runtime compiles them |
| Retries/rollback are manual | Context is git-like versioned (diff, rollback, branch, merge) |
| "Who produced this?" is lost | Provenance links every derived artifact to its inputs |
| The model guesses the numbers | Calculations are calculated — the LLM is a reasoning component, not the source of truth |
| Tracing needs a SaaS add-on | Native trace store (SQLite + dashboard), exportable to Langfuse/Postgres |
| MCP via a framework adapter | MCP both ways built in — call any server, or expose your own Context as one |
| Pulls in a framework's dependency tree | 3 core deps: pydantic, httpx, python-dotenv |
Reactive. Deterministic. Accountable.
Full breakdown, including where reactifact is not the right choice: docs/en/comparison.md.
Proof, not a claim — examples/ledger is a 4-artifact
billing calc (LaborCost, Tax, Discount, Total) with no LLM, fully
offline. Edit one fact and see what actually reruns:
>>> editing ONLY TaxRate (0.08 -> 0.12) — a fact nothing about
>>> LaborCost or Discount ever consumed.
LaborCost value=500.0 version=0 # untouched
Tax value=60.0 version=1 # recomputed
Discount value=25.0 version=0 # untouched
Total value=535.0 version=1 # recomputed
2 of 4 artifacts recompute — the 2 that actually depend on TaxRate —
because Artifact.version tracks real consumption, not a graph edge you drew
by hand. Run it yourself: uv run python -m examples.ledger.main.
Core primitives
- Context — versioned working state, git-like commits,
diff/rollback/merge. - Artifact — a first-class typed object (
Claim,Evidence,Answer), not a string blob. - Effects — an agent states its change via
self.effects.create/update/link/ask; the runtime compiles it. - Patch — the compiled, validated change-set applied as one atomic commit.
- Agent — a thin container declaring
consumes/produces; logic lives in aProduce. - Source — retrieval is a capability: vector search is one strategy, not the only one; filesystem, CSV, and the web are equally first-class today (direct API, keyword, and SQL sources are on the roadmap, not yet shipped).
- Provenance — every derived artifact links to what produced it
(
Answer —supported_by→ Claim —derived_from→ Evidence —extracted_from→ Doc). - HITL — humans as
effects.ask(...)→PendingQuestion, answered viaeffects.resume(...)like any agent. - Outbox — outbound side effects as
effects.act(...)→PendingAction, performed once after the commit byRuntime(dispatcher=...); safe across replay, retries and merged branches.
In the box
- Deterministic by design — calculations over structured data, honest
Nonefallbacks instead of hallucinated answers; the model reasons, never "knows". - Observability — every run traces agent spans, reads/writes, LLM calls,
tokens: SQLite store + web dashboard, exportable to Langfuse/Postgres (async sinks);
correlated logs (
configure_logging(), silent until called) carryrun_id/session_id/agenton every line. - Lifecycle —
Budget.max_secondsis a hard deadline (a hung LLM call is cancelled);request_stop()/ashutdown()stop at a generation boundary (RunOutcome.STOPPED);active_runs()/in_flightgive a readiness view. - MCP, both ways — call any MCP server's tools as a
Tool(mcp_stdio_tools/mcp_http_tools), or expose your ownTools and a liveContextas an MCP server (create_mcp_server) for Claude Desktop, Claude Code, or another agent to call into (mcpextra). - Budgets & replanning — cap by runs/time/iterations/tool-calls, replan on decline.
- Branching & replay —
context.branch(), three-waymerge(), deterministicReplayLLM, all for audit and safe alternative states. - Sessions —
SessionStoreoverFileKVBackend/SQLiteKVBackend(andPostgreSQLKVBackend) for durable chat memory. - Web layer —
ChatAssistant+create_chat_routermount a canonical SSE chat on your FastAPI app; errors degrade to a logged fallback, never a 500. - Recipes —
find/find_all(typed lookup ininputs),fan_out_sources,materialize_doc,StatusMachine,WindowSummarizer/WindowPruner(bounded conversation memory), change→rebuild rollback helpers,Skill/match_skills(Claude-Skills-shaped instructions, keyword-triggered) — pure and LLM-free, except the summarizer, which takes your callback. - Viz & CLI — Mermaid
blueprint/context_to_mermaid/trace_to_mermaid;reactifactwithgraph/context/trace/replay/branch.
Agent Skills
reactifact ships official Agent Skills — reusable instructions that teach a coding agent (Claude Code, Cursor, Codex, Copilot, …) how to build with reactifact. They're bundled with the package, so they always match your version:
python -m reactifact skills list # what is available
python -m reactifact skills install # -> .agents/skills/
python -m reactifact skills install --target both # + .claude/skills/
Skills: reactifact (mental model), reactifact-agents, reactifact-llm,
reactifact-rag, reactifact-testing, reactifact-eval,
reactifact-observability, reactifact-from-langchain — see
Agent skills.
Run a demo
Offline-capable, no API keys required (deterministic fallbacks):
uv run python ./examples/llm_ladder/level1.py # the simplest LLM turn (offline too)
uv run python ./examples/repair/web.py # room renovation: plan, estimate, CSV export
uv run python ./examples/devops/web.py # HITL ops assistant + trace dashboard
Classic-pattern ports run as one-liners too:
python -m examples.{reflection,map_reduce,supervisor,summarize,time_travel,plan_execute,adaptive,ledger,outbox}.main.
Prefer a notebook?
—
examples/quickstart.ipynb covers the four quick cases (agent/rag/tools_agent/chat_agent), offline by default.
Examples (in-repo, not shipped)
knowledge— multi-source chat: search → evidence → claim verification → answer, with CSV calculation.research— goes to the web (WebSource): lazy page fetch → evidence → verified claims → answer with URL provenance.medic-lab— hypothesis laboratory: competing hypotheses scored, HITL steering, honest report.devops— HITL tool agents + LLM tool router + trace dashboard.repair— budget-aware replanning (chat/data in Russian by design).chat— minimal offline single-model multi-turn conversation (Conversation+ boundedChatMemory).forklab— deterministic branch & merge: two strategies on their own forks, three-way merge.ledger— offline proof of reactive recompute: edit one fact, only its realConsumers re-run.outbox— one intent, one send:effects.act(...)+Runtime(dispatcher=...), safe across replays, retries and merged branches (§42).llm_ladder— the workflow from one LLM call to state-changing patches (3 levels).adaptive— hybrid scheduler: rule filters + deterministic rank + LLM tie-break +rank_limit.{reflection,map_reduce,supervisor,summarize,time_travel,plan_execute}— canonical ports (see port-matrix).
Documentation
- English · Русский — concepts, sources, providers, recipes, patterns, observability, eval, branching, replay, viz/CLI, API.
- Quickstart — the four
quickcases (structured call, RAG, tools, chat) in a few lines; also as a Colab notebook. - Migrating — from LangGraph / CrewAI / LlamaIndex / plain Python: concept map, port-one-node, interop, checklist.
- Why reactifact — the design argument: why effects, why no graph, why determinism.
- Comparison — reactifact vs LangGraph/CrewAI, feature by feature, and when not to use reactifact.
- Tutorial · llm-ladder — learn the workflow.
- docs/constitution.md — the full design rationale and invariants.
- Roadmap — what's next, and what's deliberately out of scope.
Development
uv sync --extra dev --extra web
.venv/bin/python -m pytest
.venv/bin/mypy
.venv/bin/ruff check
Status & contributing
Active, 0.15.x, pre-1.0 — the API is stabilizing and 1.0 will freeze it
(see the roadmap); releases follow
Keep a Changelog. Questions and design discussion live in
GitHub Discussions; a star,
or a good first issue, helps more than you'd think.
License
MIT — see LICENSE.
Metadata
Release files for reactifact 0.15.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| reactifact-0.15.3.tar.gz | 530.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| reactifact-0.15.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 964.6 kB
Release files / reactifact-0.15.3.tar.gz
| Download URL | reactifact-0.15.3.tar.gz |
|---|---|
| Size | 530.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
005a5387441da9251ad5b208127091379c6a4e487e3fd3d24bd5a857105a9b06
|
|
BLAKE2b-256 checksum How to use checksums |
bb56a15a61b7d48d8c5aa1eebca5fca5c146c46355529174bd9c858d27b02943
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / reactifact-0.15.3-py3-none-any.whl
| Download URL | reactifact-0.15.3-py3-none-any.whl |
|---|---|
| Size | 434.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eef006966296039ec385068aeb383cf12846d3c066558721db4d17f61ec45acb
|
|
BLAKE2b-256 checksum How to use checksums |
6df5c8cc47b5256b2fa32da05b9bd66b6284f37c0ac03a9ece35dc59ee3c9125
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for reactifact 0.15.3
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.