Что если собирать агентов как dbt-проект?

от автора

Я пришел в разработку агентов из дата инженерии, и в очередной раз занимаясь сборкой типовой структуры на LangGraph я заскучал по декларативному подходу, хорошо многим знакомому по dbt — там ты описываешь что ты хочешь сделать с данными, а не как. И тогда у меня появилась мысль — а почему бы не собрать свой фреймворк для агентов, который даст тот же подход.

Плюс, самое нелюбимое для меня в мультиагентных системах на LangGraph это мутабельный State. Он быстро превращается в неконтролируемую свалку в оперативной памяти, его надо каждый раз аккуратно обновлять, а когда что-то работает не так как хочется — принтовать и выискивать косяки. Возможно, я просто что-то делаю не так, но для меня такие штуки это всегда про дополнительную трату внимания. В поисках решения данной проблемы я и познакомился с применением архитектуры event driven в мультиагентных системах, а дальше — event sourced. Основным источником знаний по данной архитектуре выступила свежая научная работа «ESAA: Event Sourcing for Autonomous Agents in LLM-Based Software Engineering» (Brito dos Santos Filho, 2026). В итоге я написал свой фреймворк — zymi. Но обо всем по порядку.

Фреймворк написан на Rust — думаю нет смысла лишний раз рассказывать про его преимущества. Сразу обозначу — разработка велась с Claude Code. Можно по-разному относиться к вайбкодингу и генерации кода, но я за время разработки понял одну очень важную мысль — если ты хочешь разработать что-то актуальное, то у тебя нет других вариантов. Я начал работу над проектом в самый разгар шумихи вокруг OpenClaw, а сейчас пишу эту статью параллельно давая команды Claude Code со своего айфона. Писал бы сам — закончил бы при бесплатном и всем доступном AGI (ну то есть никогда).

Про название

Из-за всей этой скорости вокруг случилось и название проекта — изначально оно было zumi, отсылка к собачьему зумис, когда пёс вдруг начинает носиться по квартире без остановки — но где-то на второй неделе вышло приложение с эмпатичным AI питомцем с таким же названием. Я решил поменять одну букву, чтобы не было путаницы.

Декларативность

Весь кайф уже упомянутого dbt заключается как раз в том, что движок полностью забирает на себя все вопросы насчет того, как сделать то, что тебе хочется. Ты декларируешь желание — оформляешь yaml, пишешь SQL и dbt сам прогоняет все необходимые тебе преобразования. Я решил пойти по такому же пути.

Установка zymi

pip install zymi-core

И инициализируем встроенный пример проекта агента-исследователя:

zymi init --example research

Структурно дефолтный проект выглядит следующим образом:

zymi-research/├── .zymi/├── agents/│  ├── researcher.yml│  └── writer.yml├── memory/├── output/  ├── pipelines/│  └── research.yml├── tools/└── project.yml

Данный пайплайн состоит из всего двух агентов — исследователя и писателя. Вот так они оформляются:

name: researcherdescription: "Research agent — searches the web, scrapes pages, and stores findings in memory"model: ${default_model}system_prompt: |  You are a thorough research assistant. Your job is to find accurate,  up-to-date information on the given topic.  Strategy:  1. Start with broad web searches to identify key sources.  2. Scrape the most promising pages for detailed content.  3. Store important findings in memory with clear keys.  4. Cite your sources.  Always prefer primary sources over secondary ones.  If information conflicts, note the discrepancy.tools:  - web_search  - web_scrape  - write_memorymax_iterations: 15
name: writerdescription: "Writer agent — reads research findings and produces a structured report"model: ${default_model}system_prompt: |  You are a skilled technical writer. Your job is to transform raw research  findings into a clear, well-structured report.  Guidelines:  1. Read all available memory entries to understand the research.  2. Organize findings into logical sections.  3. Include a summary at the top.  4. Cite sources where available.  5. Write the final report to the output directory.  Format: Markdown. Be concise but thorough.tools:  - read_file  - write_filemax_iterations: 10

Вот так оформляется инструмент для поиска в интернете, например, с использованием Tavily

name: web_searchdescription: "Search the web for information on a given query"parameters:  type: object  properties:    query:      type: string      description: "Search query"  required: [query]implementation:  kind: http  method: POST  url: "https://api.tavily.com/search"  headers:    Content-Type: "application/json"    Authorization: "Bearer ${env.TAVILY_API_KEY}"  body_template: '{"query": "${args.query}", "max_results": 5}'

После чего агентов можно собрать в единый пайплайн

name: researchdescription: "Multi-step research pipeline: parallel search → analysis → report"steps:  - id: search_web    agent: researcher    task: "Search the web for information about: ${inputs.topic}"  - id: search_deep    agent: researcher    task: "Find in-depth articles and technical details about: ${inputs.topic}"  - id: analyze    agent: researcher    task: >      Analyze and cross-reference all findings from the web search and deep      search. Identify key themes, contradictions, and gaps. Store a structured      summary in memory under the key 'analysis'.    depends_on:      - search_web      - search_deep  - id: write_report    agent: writer    task: >      Read the analysis from memory and write a comprehensive research report      to ./output/report.md. Include an executive summary, main findings,      and a sources section.    depends_on:      - analyzeinput:  type: textoutput:  step: write_report

Собственно всё — простенькая мультиагентная система готова. Можем её тут же запустить через консоль:

❯ zymi run research -i topic="Event sourcing in AI"Pipeline: research  Multi-step research pipeline: parallel search → analysis → report  Execution plan: 4 steps, 3 levels  Level 1 (parallel): search_deep, search_web    [search_deep] done (2 iterations)    [search_web] done (3 iterations)  Level 2: analyze    [analyze] done (2 iterations)  Level 3: write_report--- approval required ----------------------------  File write outside allowed directories: output/report.md  approve? [y/N]: y    [write_report] done (2 iterations)---Pipeline completed successfully.Final output:The comprehensive research report on Event Sourced Architecture in Multi-Agent Systems has been successfully written to `./output/report.md`. It includes an executive summary, detailed findings on key themes, potential contradictions, and areas for further exploration, as well as a section on sources.

Кроме удобства и скорости — что индивидуально, конечно — это дает гораздо более важное преимущество для современного мира: генеративные модели прекрасно справляются с генерацией кода, но генерация yaml, да ещё по строгой json схеме, эта задача для них на порядок проще. У меня запланирован эксперимент, о котором я напишу в следующей статье — попрошу Claude/Codex сгенерировать один и тот же пайплайн на LangGraph и на zymi, и запустить его. Ставлю на то, что zymi потребует меньше итераций и токенов.

Но декларативный конфиг — это только внешний интерфейс. Под капотом zymi тоже отличается от того же LangGraph — вместо общего стейта, который мутируется агентами, всё общение построено на единой шине данных.

Event sourced архитектура

В zymi каждое событие — это иммутабельная запись в БД с hash-chain верификацией. Все взаимодействия строятся на таких событиях — записях в шине данных. Все коннекторы, агенты, инструменты пишут и читают эту шину, она является единственным источником правды в системе. В итоге, мы получаем не просто лог, а криптографически связанную цепочку всех действий агента. Это позволяет из коробки делать каждый запуск прослеживаемым и воспроизводимым.

Зачем нам тут криптография? Можно провести аналогию как раз с dbt: лог событий даёт нам lineage — dbt docs; намерения и верификация дают уверенность в корректности этого лога — dbt test.

Благодаря этому, мы всегда знаем не только что агент сделал, но и почему — вместо того, чтобы напрямую что-то изменять они выражают намерение это сделать. Это намерение также пишется в шину данных откуда его получает монитор — и уже он, на базе своих требований, решает можно ли это выполнить или может надо спросить пользователя. И в самом конце, события 49-50, как раз можно увидеть как монитор засомневался стоит ли разрешать запись в директорию за пределами разрешенных и позвал человека.

Посмотреть лог запуска

zymi events --verbose
Получаем вот такой длинный подробный лог
Stream 'pipeline-research-04e6e68e-622c-4233-8a2d-2051fd132b11': 50 event(s)#1    10:50:51.317 workflow_started source=engine  4 node(s) — pipeline: research  #2    10:50:51.320 workflow_node_started source=engine    search_deep — agent=researcher, task="Find in-depth articles and technical details about: Even  #3    10:50:51.320 workflow_node_started source=engine    search_web — agent=researcher, task="Search the web for information about: Event sourcing in    #4    10:50:52.806 intention_emitted source=orchestrator      call_custom_tool data={"type":"CallCustomTool","data":{"tool_name":"web_search","arguments":"{\"query\":\"Event sourcing in AI\"}"}}    #5    10:50:52.806 intention_evaluated source=orchestrator      call_custom_tool -> approved    #6    10:50:52.981 intention_emitted source=orchestrator      call_custom_tool data={"type":"CallCustomTool","data":{"tool_name":"web_search","arguments":"{\"query\":\"Event sourcing in AI\"}"}}    #7    10:50:52.982 intention_evaluated source=orchestrator      call_custom_tool -> approved    #8    10:50:56.138 intention_emitted source=orchestrator      call_custom_tool data={"type":"CallCustomTool","data":{"tool_name":"web_scrape","arguments":"{\"url\": \"https://www.eventsourcing.ai/\"}"}}    #9    10:50:56.139 intention_evaluated source=orchestrator      call_custom_tool -> approved    #10   10:50:57.055 intention_emitted source=orchestrator      call_custom_tool data={"type":"CallCustomTool","data":{"tool_name":"web_scrape","arguments":"{\"url\": \"https://www.eventsourcing.ai/\"}"}}    #11   10:50:57.055 intention_evaluated source=orchestrator      call_custom_tool -> approved    #12   10:50:58.860 intention_emitted source=orchestrator      call_custom_tool data={"type":"CallCustomTool","data":{"tool_name":"web_scrape","arguments":"{\"url\": \"https://infodation.com/en/blogs/event    #13   10:50:58.861 intention_evaluated source=orchestrator      call_custom_tool -> approved    #14   10:50:59.261 intention_emitted source=orchestrator      call_custom_tool data={"type":"CallCustomTool","data":{"tool_name":"web_scrape","arguments":"{\"url\": \"https://infodation.com/en/blogs/event    #15   10:50:59.262 intention_evaluated source=orchestrator      call_custom_tool -> approved    #16   10:51:03.028 intention_emitted source=orchestrator      call_custom_tool data={"type":"CallCustomTool","data":{"tool_name":"web_scrape","arguments":"{\"url\": \"https://artium.ai/insights/event-sour    #17   10:51:03.031 intention_evaluated source=orchestrator      call_custom_tool -> approved    #18   10:51:03.105 intention_emitted source=orchestrator      call_custom_tool data={"type":"CallCustomTool","data":{"tool_name":"web_scrape","arguments":"{\"url\": \"https://artium.ai/insights/event-sour    #19   10:51:03.106 intention_evaluated source=orchestrator      call_custom_tool -> approved    #20   10:51:05.744 intention_emitted source=orchestrator      call_custom_tool data={"type":"CallCustomTool","data":{"tool_name":"web_scrape","arguments":"{\"url\": \"https://akka.io/blog/event-sourcing-t    #21   10:51:05.745 intention_evaluated source=orchestrator      call_custom_tool -> approved    #22   10:51:16.668 intention_emitted source=orchestrator      write_memory data={"type":"WriteMemory","data":{"key":"event_sourcing_ai_overview","content":"Event sourcing in AI involves using event-dr    #23   10:51:16.669 intention_evaluated source=orchestrator      write_memory -> approved  #24   10:51:16.669 memory_written source=agent    event_sourcing_ai_overview = Event sourcing in AI involves using event-driven architecture to continuously ca    #25   10:51:16.671 intention_emitted source=orchestrator      write_memory data={"type":"WriteMemory","data":{"key":"event_sourcing_ai_benefits_challenges","content":"The integration of AI into event    #26   10:51:16.672 intention_evaluated source=orchestrator      write_memory -> approved  #27   10:51:16.674 memory_written source=agent    event_sourcing_ai_benefits_challenges = The integration of AI into event sourcing enhances decision-making, predictive a    #28   10:51:16.675 intention_emitted source=orchestrator      write_memory data={"type":"WriteMemory","data":{"key":"event_sourcing_key_components","content":"Event sourcing is a data storage pattern    #29   10:51:16.676 intention_evaluated source=orchestrator      write_memory -> approved  #30   10:51:16.676 memory_written source=agent    event_sourcing_key_components = Event sourcing is a data storage pattern where every change is recorded as an ev    #31   10:51:16.678 intention_emitted source=orchestrator      write_memory data={"type":"WriteMemory","data":{"key":"event_sourcing_vs_cqrs","content":"Event sourcing is often confused with Command Qu    #32   10:51:16.679 intention_evaluated source=orchestrator      write_memory -> approved  #33   10:51:16.679 memory_written source=agent    event_sourcing_vs_cqrs = Event sourcing is often confused with Command Query Responsibility Segregation (    #34   10:51:16.684 intention_emitted source=orchestrator      write_memory data={"type":"WriteMemory","data":{"key":"event_sourcing_agentic_ai","content":"Event sourcing provides a strong foundation f    #35   10:51:16.686 intention_evaluated source=orchestrator      write_memory -> approved  #36   10:51:16.688 memory_written source=agent    event_sourcing_agentic_ai = Event sourcing provides a strong foundation for agentic AI systems by supporting    #37   10:51:16.690 intention_emitted source=orchestrator      write_memory data={"type":"WriteMemory","data":{"key":"event_sourcing_ai_sources","content":"Sources for event sourcing in AI include Even    #38   10:51:16.690 intention_evaluated source=orchestrator      write_memory -> approved  #39   10:51:16.691 memory_written source=agent    event_sourcing_ai_sources = Sources for event sourcing in AI include EventSourcing.ai, Infodation, Artium.ai  #40   10:51:21.170 workflow_node_completed source=engine    ok search_web  #41   10:51:24.346 workflow_node_completed source=engine    ok search_deep  #42   10:51:24.346 workflow_node_started source=engine    analyze — agent=researcher, task="Analyze and cross-reference all findings from the web se    #43   10:51:29.794 intention_emitted source=orchestrator      write_memory data={"type":"WriteMemory","data":{"key":"analysis","content":"### Key Themes\n1. **Traceability and Reproducibility**: Event    #44   10:51:29.794 intention_evaluated source=orchestrator      write_memory -> approved  #45   10:51:29.795 memory_written source=agent    analysis = ### Key Themes1. **Traceability and Reproducibility**: Event sourcing allows fo  #46   10:51:30.901 workflow_node_completed source=engine    ok analyze  #47   10:51:30.903 workflow_node_started source=engine    write_report — agent=writer, task="Read the analysis from memory and write a comprehensive rese    #48   10:51:37.515 intention_emitted source=orchestrator      write_file data={"type":"WriteFile","data":{"path":"output/report.md","content":"# Research Report on Event Sourcing in AI\n\n## Executi    #49   10:51:37.516 intention_evaluated source=orchestrator      write_file -> requires_approval: File write outside allowed directories: output/report.md    #50   10:51:37.517 approval_requested source=orchestrator      ⏳ waiting id=9869501e-141a-48bb-84a8-37f1a271b42f File write outside allowed directories: output/report.md

Заключение

Конечно zymi еще сырой — это только альфа, но он уже готов помогать прототипировать и экспериментировать с агентами. У проекта большой бэклог:

  1. Переезд на libsql — векторная память, нативная асинхронщина и edge-реплики

  2. Доработка возможности подключения PostgreSQL как шины данных

  3. Реализация возможности добавления python-инструментов в декларативном режиме,

  4. Доработка проекций диалогов для идемпотентных перезапусков

  5. Стриминг ответов LLM И много чего ещё.

Мне интересно посмотреть, найдет ли этот дата инженерный подход отклик в современном мире AI агентов. Буду рад любой обратной связи — залетайте в комментарии и в репозиторий проекта (ставить звёзды, конечно).

И да пребудет с вами иммутабельность!

ссылка на оригинал статьи https://habr.com/ru/articles/1025028/