reactifact¶
Событийные агенты для Python-разработчиков — задачи просыпаются на типизированных артефактах, как задачи Celery просыпаются на сообщениях. Граф рисовать не нужно.
Python-разработчик уже знает эту модель по Celery: объяви задачу, скажи, что
её запускает, — рантайм сам её выполнит. reactifact применяет это к агентам:
задача реагирует на появление типизированного версионируемого артефакта в
контексте, а не на сообщение, которое вы пушите, и не на ребро графа. Рантайм
выводит, что запустится дальше, из состояния.
| Celery | reactifact |
|---|---|
| задача (task) | @produce(Model) — единица работы, пишущая артефакт |
delay() / apply_async() |
вызывать не нужно: создание входного артефакта и есть триггер |
| routing key / очередь | Consume(Type) — какой тип артефакта будит задачу |
| chain / group / chord | несколько consumes / produces; порядок рантайм выводит сам |
retries, acks_late |
гарды + Budget, честный None вместо неверного результата |
| result backend | Context — типизированные версионируемые артефакты |
| worker | Runtime |
Сегодня — один процесс (без брокера и пула воркеров): Celery-образная модель, а не его распределённый рантайм.
Сверх этой модели вы получаете то, чего у очереди задач нет: каждый артефакт
версионируется с провенансом, поэтому запуск воспроизводим (context_hash) и
аудируем (audit.report) бесплатно. Модель рассуждает; детерминированные части
остаются в коде; каждое утверждение несёт провенанс.
Сверх этого: проверяемый ответ¶
examples/fintech_audit — CSV транзакций, CSV
бюджета и документ политики, без API-ключа. Число никогда не считает модель;
это делает обычный Python, а ответ связан со своими доказательствами:
variance vs budget: +12.5% ($45,000 факт vs $40,000 бюджет, порог 10% → превышен)
answer sha256 5461290d… · context sha256 24449f6f…
повторный прогон даёт тот же хеш — или проверьте сохранённый запуск:
reactifact replay <store> --session <id> --verify 24449f6f…

Как это работает¶
EVENT (created/updated)
│
▼
CONTEXT ───────► ARTIFACTS ──► AGENTS REACT ──self.effects──► EFFECTS
▲ │ │
│ │ │ compile
└────── CONTEXT' ◄────────────────┘ PATCH ◄────────────────────┘
Вы описываете какие данные существуют, какие артефакты есть и что агенты могут с ними делать. Остальное делает runtime.
Ментальная модель¶
| Традиционный агент | reactifact |
|---|---|
| Программа идёт по графу/плану | Агенты реагируют на изменения состояния |
| Сообщения — строки | Типизированные артефакты (Claim, Evidence, Answer, …) |
| Оркестрация явная | Оркестрация выводится из состояния |
| Компонент возвращает результат | Produce пишет effects (self.effects) — компилирует runtime |
| Ретраи/откаты вручную | Контекст версионируется (коммиты как в git, diff, rollback) |
| «Кто это произвёл?» теряется | Провенанс связывает каждый производный артефакт с его входами |
Почему effects вместо «вернуть изменение»?¶
В центре цикла — то, как produce вносит изменение. Многие фреймворки просят производителя вернуть результат, а какой-то оркестратор применяет его.
reactifact переворачивает авторство: produce формулирует, что должно
измениться, через self.effects (create / update / link / ask) и возвращает
None; runtime компилирует набор эффектов в один атомарный патч — либо
применяется весь шаг, либо ничего.
async def produce(self, call: ProduceCall) -> None:
evidence = self.effects.create(Evidence(...), id="evidence:q1")
answer = self.effects.create(Answer(...), id="answer:q1")
evidence.link("extracted_from", doc)
answer.link("supported_by", evidence)
self.effects.update(turn, status="answered")
return None
Поскольку handle — это объекты, а не id, одно выражение может ссылаться на
артефакт, созданный другим. А так как компиляцией владеет runtime, ручной
сборки Patch не бывает. Участие человека — просто ещё один эффект
(effects.ask(...)). Подробнее — в Почему reactifact и в
контракте produce.
Почему артефакты вместо сообщений?¶
Сообщения непрозрачны; артефакты инспектируемы. Объект Evidence знает свой
текст, источник и оценку. Благодаря провенансу runtime отвечает на вопрос
«почему агент так сказал?», проходя по связям
Answer —supported_by→ Claim —derived_from→ Evidence —extracted_from→ Doc.
Почему версионируемый контекст?¶
Каждый запуск — это коммит:
- Diff — что именно изменилось между двумя ходами.
- Rollback — отменить неудачный шаг и перезапустить с чистого состояния.
- Детерминированные повторы — та же история даёт тот же результат.
- Инспектируемость — полная, запрашиваемая история всего, что произошло.
Требования¶
- Python 3.11–3.14 (
.venvуправляется черезuv; CI гоняет весь набор тестов на всех четырёх). pydanticдля моделей артефактов; FastAPI/uvicorn — только для web-примеров.
Быстрый старт¶
Два агента, и между ними не объявлено ни одной связи в графе — второй реагирует потому, что появился результат первого, а ответ несёт доказательство того, откуда он взялся:
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": "Возврат возможен в течение 14 дней с момента покупки.",
"pricing": "Тариф Pro — $49/месяц при годовой оплате.",
}
@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="какая у вас политика возврата?"))
runtime.run() # оба агента реагируют сами — никто их не связывал вручную
answer = ctx.latest(Answer)
evidence = ctx.related(answer.id, "supported_by")[0]
print(answer.data.text) # "Возврат возможен в течение 14 дней с момента покупки."
print("supported_by:", evidence.data.text) # провенанс, который можно проследить, а не строка в логе
Это весь цикл: создан артефакт → агенты реагируют → применён патч → версия контекста продвинулась. Всё остальное в этой документации надстраивается над этим циклом.
Хотите чего-то ближе к реальному приложению? Quickstart — три рабочих, проверенных сниппета: tool-calling агент, поиск по своим документам, чат-бот с сохраняемыми сессиями — каждый со ссылкой на полный пример, из которого он урезан.
Куда дальше¶
Понять идею
- Почему reactifact — дизайн-аргумент: почему effects, почему без графа, почему детерминизм, почему версионируемое состояние.
- Сравнение — reactifact vs LangGraph/CrewAI по пунктам, и когда reactifact не стоит использовать.
- Concepts — Context, Artifact, Patch, Agent, Produce.
Строить на этом
- Quickstart — три рабочих сниппета: tool-calling агент, поиск по своим документам, чат-бот с сохраняемыми сессиями.
- Sources — откуда агенты берут информацию.
- Providers — как подключить LLM/эмбеддер/провайдер
изображений/речи в
RuntimeResources. - Recipes — готовые search fan-out, материализация референсов, машины состояний жизненного цикла.
- Patterns — переиспользуемые паттерны (reflection, map-reduce, supervisor, …), каждый — на конкретном примере.
Эксплуатировать
- Observability — трейс каждого запуска: агентские спаны, чтения/записи, LLM-вызовы; офлайн-дашборд или экспорт в Langfuse/Postgres.
- Диагностика проблем — «агент не запустился» / «запустился дважды» / «run остановился раньше времени» — по симптомам, не по фичам.
- Evaluation — многоуровневая оценка финального
Context(качество evidence, provenance grounding, корректность вычислений, …). - Branching & merge — форк состояния, исследование альтернатив, трёхстороннее слияние с явными конфликтами.
- Replay — детерминированная реконструкция «почему агент ответил именно так», без повторного запуска агентов.
- Visualization & CLI — Mermaid-диаграммы графа артефактов и трейса
запуска; CLI-утилиты
reactifact. - API reference — все верхнеуровневые символы, по строке на каждый.
Посмотреть в деле
- Examples — четырнадцать работающих приложений, которые можно запустить.
- Port matrix — какой классический паттерн LangGraph/CrewAI/DSPy соответствует какому примеру.
- Design notes — более глубокое обоснование адаптивного планировщика; модель компиляции effects → Patch описана в English design note (пока не переведена — это архивный лог обсуждения, актуальное поведение уже в effects.md).