Skip to main content

reactifact

Stop drawing the graph. Build agents as reactions to versioned, provable artifacts.

CI Python PyPI version License: MIT

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.

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.

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

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

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 a Produce.
  • 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 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 & replaycontext.branch(), three-way merge(), deterministic ReplayLLM, all for audit and safe alternative states.
  • SessionsSessionStore over FileKVBackend/SQLiteKVBackend (and PostgreSQLKVBackend) for durable chat memory.
  • Web layerChatAssistant + create_chat_router mount a canonical SSE chat on your FastAPI app; errors degrade to a logged fallback, never a 500.
  • Recipesfind/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,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 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} — 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

reactifact-0.6.1.tar.gz (236.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reactifact-0.6.1-py3-none-any.whl (189.9 kB view details)

Uploaded Python 3

File details

Details for the file reactifact-0.6.1.tar.gz.

File metadata

  • Download URL: reactifact-0.6.1.tar.gz
  • Upload date:
  • Size: 236.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.31

File hashes

Hashes for reactifact-0.6.1.tar.gz
Algorithm Hash digest
SHA256 6d713df77b6884f0fb7f7e82b526fd47991138200d98194ffef066a6cf7d0536
MD5 5a2b9f17c6fbd4dce78dfc7a3db24529
BLAKE2b-256 959ea09a84761add953583b0f9bcb084c86e29c280526044a12e7ce9ede5ec2f

See more details on using hashes here.

File details

Details for the file reactifact-0.6.1-py3-none-any.whl.

File metadata

  • Download URL: reactifact-0.6.1-py3-none-any.whl
  • Upload date:
  • Size: 189.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.31

File hashes

Hashes for reactifact-0.6.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a7919b38d998ecbe1745cbb0aa7fe60018050260d0804de4719d108e8d557609
MD5 b0f6540c406617f4b02d96635c6a483a
BLAKE2b-256 33913980e293a54507bd34fbb756ed40023fc39230d393f5e679e7f97374165e

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.6.1 This release

2 files

0.6.0

2 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