reactifact
Stop drawing the graph. Build agents as reactions to versioned, provable artifacts.
Most agent frameworks make you draw the graph: connect nodes, wire memory, declare control flow. But a knowledge question — "why did infra costs jump in Q2?" — needs Confluence + GitLab + CSV + calculations + verification, and the next question needs a different path. There is no universal graph to draw.
reactifact flips the model. You describe what artifacts exist and what agents can do with them; the runtime derives what runs next from state changes. Agents react to events — there is no graph, no node pipeline.
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(context, inputs, event, effects):
question = next((a for a in 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:
effects.create(Evidence(text=hit))
@produce(Answer)
async def answer_from_evidence(context, inputs, event, effects):
evidence = next((a for a in inputs if isinstance(a.data, Evidence)), None)
if evidence is None:
return None
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 |
Reactive. Deterministic. Accountable.
Full breakdown, including where reactifact is not the right choice: docs/en/comparison.md.
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; direct API, keyword, SQL, filesystem are equally first-class.
- 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.
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).
- 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.
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,adaptive,ledger}.main.
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).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.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}— canonical ports (see port-matrix).
Documentation
- English · Русский — concepts, sources, providers, recipes, patterns, observability, eval, branching, replay, viz/CLI, API.
- Quickstart — three runnable snippets: tool-calling agent, retrieval over your docs, session-persisted chat bot.
- 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.
Development
uv sync --extra dev --extra web
.venv/bin/python -m pytest
.venv/bin/mypy
.venv/bin/ruff check
License
MIT — see LICENSE.
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 reactifact-0.6.0.tar.gz.
File metadata
- Download URL: reactifact-0.6.0.tar.gz
- Upload date:
- Size: 228.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.5.31
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b53b62e535958efb8871095017f6d2503e028009554a50b9091ee40b99e65263
|
|
| MD5 |
6e43989931f07e94e2c40433c8ea1b30
|
|
| BLAKE2b-256 |
08ecfaa134102e2c2938a24e7f883fac5a680354534cae8a53798c0d3a65ea46
|
File details
Details for the file reactifact-0.6.0-py3-none-any.whl.
File metadata
- Download URL: reactifact-0.6.0-py3-none-any.whl
- Upload date:
- Size: 184.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.5.31
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d98a50d19fa1c0c44ded0f0e1790e2127d5d7b120eedc4344c674fa70c71aa4f
|
|
| MD5 |
87220c8eb6b7452df9bdb109025bf6cc
|
|
| BLAKE2b-256 |
4ffa8d4f744a521558ea475604ddca7859dadae324a094c45659d4ca1fc50147
|