Skip to main content

reactifact — Agents that react to artifacts, not graphs

Event-driven agents for Python developers — tasks wake on typed artifacts, like Celery tasks wake on messages. No graph to draw.

CI codecov Python PyPI version License: MIT Ask DeepWiki Docs

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
retries, acks_late guards + Budget, an honest None instead of a wrong result
result backend the Context — typed, versioned artifacts
worker Runtime

Single process today (no broker, no worker pool) — the model is Celery-shaped, not its distributed runtime.

On top of that model you get something a task queue doesn't: every artifact is versioned with provenance, so a run is reproducible (context_hash) and auditable (audit.report) for free. The model reasons; the arithmetic stays deterministic; every claim carries provenance.

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.

fintech_audit demo: the variance, the answer, and the reproducible context hash — a second run prints the same hash.

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

Left: a hand-wired fetch → verify → answer pipeline. Right: reactifact — search_agent and answer_agent each declare only what they consume and produce, wired together by Context, never each other.

Two agents explore independently on their own forks and merge back automatically — and when they disagree, reactifact refuses to merge silently:

forklab demo: two strategies (depth/breadth) investigate on separate forks and merge cleanly; a second run edits the same artifact on both forks and reactifact raises MergeConflict instead of guessing, then re-merges under an explicit policy.

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

The same idea, live — the knowledge example's CLI answering a harder, multi-source question (docs + a CSV) with a real computed number and its sources, no LLM key required:

CLI demo: asking "how much does gpu cost in total?" — the runtime searches docs and a spreadsheet, computes the sum, verifies it, and answers with sources.

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 a Produce.
  • 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 via effects.resume(...) like any agent.

In the box

  • Deterministic by design — calculations over structured data, honest None fallbacks 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).
  • MCP, both ways — call any MCP server's tools as a Tool (mcp_stdio_tools/mcp_http_tools), or expose your own Tools and a live Context as an MCP server (create_mcp_server) for Claude Desktop, Claude Code, or another agent to call into (mcp extra).
  • Budgets & replanning — cap by runs/time/iterations/tool-calls, replan on decline.
  • Branching & replay — context.branch(), three-way merge(), deterministic ReplayLLM, all for audit and safe alternative states.
  • Sessions — SessionStore over FileKVBackend/SQLiteKVBackend (and PostgreSQLKVBackend) for durable chat memory.
  • Web layer — ChatAssistant + create_chat_router mount a canonical SSE chat on your FastAPI app; errors degrade to a logged fallback, never a 500.
  • Recipes — find/find_all (typed lookup in inputs), 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; reactifact with graph/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,plan_execute,adaptive,ledger}.main.

Prefer a notebook? Open in Colab — 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).
  • forklab — deterministic branch & merge: two strategies on their own forks, three-way merge.
  • ledger — offline proof of reactive recompute: edit one fact, only its real Consumers 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,plan_execute} — canonical ports (see port-matrix).

Documentation

  • English · Русский — concepts, sources, providers, recipes, patterns, observability, eval, branching, replay, viz/CLI, API.
  • Quickstart — the four quick cases (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

License

MIT — see LICENSE.

Release files for reactifact 0.11.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for reactifact 0.11.1
File Size Uploaded
reactifact-0.11.1.tar.gz 371.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for reactifact 0.11.1
File Interpreter ABI Platform
reactifact-0.11.1-py3-none-any.whl Python 3 none any Details

Total release size: 669.8 kB

Release files / reactifact-0.11.1.tar.gz

Download URL reactifact-0.11.1.tar.gz
Size 371.4 kB
Tags Source
SHA-256 checksum
How to use checksums
0c0a00531f369e10f4d0422c6717d467880dff4854b23f66e72b37fe59b76cf9
BLAKE2b-256 checksum
How to use checksums
0db2ea957eb8ced6c8d83c4cc2809cca93b850da0d9a74ae2d52127edc2045e2
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.11.1-py3-none-any.whl

Download URL reactifact-0.11.1-py3-none-any.whl
Size 298.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2b7a3409be6a33a0b51e79bc62de2cb25f0e9fafa1406e89aa98f68f9124d42d
BLAKE2b-256 checksum
How to use checksums
e8abea0004c4493f2a44ab8a09df08c85796f60c6a697ed1482416868822e891
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 history Release notifications | RSS feed

0.12.0

2 release files

0.11.2

2 release files

This release

0.11.1 This release

2 release files

0.11.0

2 release files

0.10.0

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.1

2 release files

0.6.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page