Files
microdao-daarion/site/tasks/TASK_PHASE2_AGENT_INTEGRATION/index.html
Apple ef3473db21 snapshot: NODE1 production state 2026-02-09
Complete snapshot of /opt/microdao-daarion/ from NODE1 (144.76.224.179).
This represents the actual running production code that has diverged
significantly from the previous main branch.

Key changes from old main:
- Gateway (http_api.py): expanded from ~40KB to 164KB with full agent support
- Router: new /v1/agents/{id}/infer endpoint with vision + DeepSeek routing
- Behavior Policy: SOWA v2.2 (3-level: FULL/ACK/SILENT)
- Agent Registry: config/agent_registry.yml as single source of truth
- 13 agents configured (was 3)
- Memory service integration
- CrewAI teams and roles

Excluded from snapshot: venv/, .env, data/, backups, .tgz archives

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 08:46:46 -08:00

1181 lines
46 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="canonical" href="https://IvanTytar.github.io/microdao-daarion/tasks/TASK_PHASE2_AGENT_INTEGRATION/">
<link rel="icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.5.3, mkdocs-material-9.5.18">
<title>TASK: PHASE 2 — Agent Integration (agent_filter + DAGI Router + Agent Runtime) - DAARION Documentation</title>
<link rel="stylesheet" href="../../assets/stylesheets/main.66ac8b77.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL("../..",location),__md_hash=e=>[...e].reduce((e,_)=>(e<<5)-e+_.charCodeAt(0),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#task-phase-2-agent-integration-agent_filter-dagi-router-agent-runtime" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../.." title="DAARION Documentation" class="md-header__button md-logo" aria-label="DAARION Documentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54Z"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2Z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
DAARION Documentation
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
TASK: PHASE 2 — Agent Integration (agent_filter + DAGI Router + Agent Runtime)
</span>
</div>
</div>
</div>
<script>var media,input,key,value,palette=__md_get("__palette");if(palette&&palette.color){"(prefers-color-scheme)"===palette.color.media&&(media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']"),palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent"));for([key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12Z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../.." title="DAARION Documentation" class="md-nav__button md-logo" aria-label="DAARION Documentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54Z"/></svg>
</a>
DAARION Documentation
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../public/" class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../public/getting-started/" class="md-nav__link">
<span class="md-ellipsis">
Getting Started
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../public/architecture-overview/" class="md-nav__link">
<span class="md-ellipsis">
Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../public/daiS_daos_overview/" class="md-nav__link">
<span class="md-ellipsis">
DAIS & DAOS
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5" >
<label class="md-nav__link" for="__nav_5" id="__nav_5_label" tabindex="">
<span class="md-ellipsis">
Internal
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_5_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5">
<span class="md-nav__icon md-icon"></span>
Internal
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5_1" >
<label class="md-nav__link" for="__nav_5_1" id="__nav_5_1_label" tabindex="0">
<span class="md-ellipsis">
Infra
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_5_1_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5_1">
<span class="md-nav__icon md-icon"></span>
Infra
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../internal/infra/INFRA_AUTOMATION_PACK_V1/" class="md-nav__link">
<span class="md-ellipsis">
Infra Automation Pack v1
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../internal/infra/monitoring_overview/" class="md-nav__link">
<span class="md-ellipsis">
Monitoring Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../internal/infra/nodes_registry_v0/" class="md-nav__link">
<span class="md-ellipsis">
Nodes Registry v0
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5_2" >
<label class="md-nav__link" for="__nav_5_2" id="__nav_5_2_label" tabindex="0">
<span class="md-ellipsis">
Specs
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_5_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5_2">
<span class="md-nav__icon md-icon"></span>
Specs
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../internal/specs/matrix_presence_aggregator/" class="md-nav__link">
<span class="md-ellipsis">
Matrix Presence Aggregator
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../internal/specs/city_map_spec/" class="md-nav__link">
<span class="md-ellipsis">
City Map Spec
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../internal/specs/node_join_protocol_draft/" class="md-nav__link">
<span class="md-ellipsis">
Node Join Protocol (Draft)
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#1-agent_filter" class="md-nav__link">
<span class="md-ellipsis">
1) Сервіс agent_filter
</span>
</a>
<nav class="md-nav" aria-label="1) Сервіс agent_filter">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#11-models-modelspy" class="md-nav__link">
<span class="md-ellipsis">
1.1 Models (models.py)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#12-rules-rulespy" class="md-nav__link">
<span class="md-ellipsis">
1.2 Rules (rules.py)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#13-mainpy" class="md-nav__link">
<span class="md-ellipsis">
1.3 main.py
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#14-dockerfile-readme" class="md-nav__link">
<span class="md-ellipsis">
1.4 Dockerfile + README
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#2-dagi-router-messaginginbound" class="md-nav__link">
<span class="md-ellipsis">
2) Розширення DAGI Router під messaging.inbound
</span>
</a>
<nav class="md-nav" aria-label="2) Розширення DAGI Router під messaging.inbound">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#21-nats-subscription" class="md-nav__link">
<span class="md-ellipsis">
2.1 NATS subscription
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#22" class="md-nav__link">
<span class="md-ellipsis">
2.2 Моделі
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#23" class="md-nav__link">
<span class="md-ellipsis">
2.3 Конфіг
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#24-http-debug-endpoint" class="md-nav__link">
<span class="md-ellipsis">
2.4 HTTP debug endpoint
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#3-agent-runtime-integration-messenger" class="md-nav__link">
<span class="md-ellipsis">
3) Agent Runtime integration з Messenger
</span>
</a>
<nav class="md-nav" aria-label="3) Agent Runtime integration з Messenger">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#31-models-modelspy" class="md-nav__link">
<span class="md-ellipsis">
3.1 Models (models.py)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#32-nats-subscription" class="md-nav__link">
<span class="md-ellipsis">
3.2 NATS subscription
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#33-http-debug-endpoint" class="md-nav__link">
<span class="md-ellipsis">
3.3 HTTP debug endpoint
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#34-docker-readme" class="md-nav__link">
<span class="md-ellipsis">
3.4 Docker + README
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#4-docker-compose" class="md-nav__link">
<span class="md-ellipsis">
4) Інтеграція в docker-compose та документацію
</span>
</a>
<nav class="md-nav" aria-label="4) Інтеграція в docker-compose та документацію">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#41-docker-compose" class="md-nav__link">
<span class="md-ellipsis">
4.1 docker-compose
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#42" class="md-nav__link">
<span class="md-ellipsis">
4.2 Документація
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#acceptance-criteria" class="md-nav__link">
<span class="md-ellipsis">
Acceptance Criteria
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="task-phase-2-agent-integration-agent_filter-dagi-router-agent-runtime">TASK: PHASE 2 — Agent Integration (agent_filter + DAGI Router + Agent Runtime)<a class="headerlink" href="#task-phase-2-agent-integration-agent_filter-dagi-router-agent-runtime" title="Permanent link">&para;</a></h1>
<p><strong>Goal:</strong>
Зробити Messenger повноцінно агентним:
- новий сервіс agent_filter, який вирішує, коли й який агент відповідає;
- розширити DAGI Router, щоб маршрутизувати події з Messenger до агентів;
- реалізувати agent-runtime-service, який читає історію каналів, викликає LLM і постить відповіді назад у Messenger.</p>
<p><strong>Existing:</strong>
- Messenger Module (Matrix-aware, Full Stack) вже реалізований:
- messaging-service (FastAPI, 9 endpoints + WS)
- matrix-gateway API spec (services/matrix-gateway/API_SPEC.md)
- DB schema (channels, messages, channel_members, message_reactions, channel_events)
- frontend /messenger (ChannelList, MessageList, MessageComposer, WS)
- NATS, Synapse, matrix-gateway, messaging-service у docker-compose.messenger.yml
- Документація:
- docs/MESSENGER_MODULE_COMPLETE.md
- docs/MESSAGING_ARCHITECTURE.md
- docs/messaging-erd.dbml
- docs/MESSENGER_TESTING_GUIDE.md</p>
<p><strong>PHASE 2 складається з 3 підзадач:</strong></p>
<hr />
<h2 id="1-agent_filter">1) Сервіс agent_filter<a class="headerlink" href="#1-agent_filter" title="Permanent link">&para;</a></h2>
<p><strong>Create new service:</strong> <code>services/agent-filter/</code></p>
<p><strong>Files:</strong>
- <code>services/agent-filter/main.py</code>
- <code>services/agent-filter/models.py</code>
- <code>services/agent-filter/rules.py</code>
- <code>services/agent-filter/config.yaml</code>
- <code>services/agent-filter/requirements.txt</code>
- <code>services/agent-filter/Dockerfile</code>
- <code>services/agent-filter/README.md</code></p>
<p><strong>Tech:</strong>
- Python + FastAPI
- NATS JetStream client (python-nats)
- Config з YAML</p>
<h3 id="11-models-modelspy">1.1 Models (models.py)<a class="headerlink" href="#11-models-modelspy" title="Permanent link">&para;</a></h3>
<p>Define Pydantic models:</p>
<div class="codehilite"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Literal</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">datetime</span><span class="w"> </span><span class="kn">import</span> <span class="n">datetime</span>
<span class="k">class</span><span class="w"> </span><span class="nc">MessageCreatedEvent</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">channel_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">message_id</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">matrix_event_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">sender_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">sender_type</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="s2">&quot;human&quot;</span><span class="p">,</span> <span class="s2">&quot;agent&quot;</span><span class="p">]</span>
<span class="n">microdao_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">created_at</span><span class="p">:</span> <span class="n">datetime</span>
<span class="k">class</span><span class="w"> </span><span class="nc">FilterDecision</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">channel_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">message_id</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">matrix_event_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">microdao_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">decision</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="s2">&quot;allow&quot;</span><span class="p">,</span> <span class="s2">&quot;deny&quot;</span><span class="p">,</span> <span class="s2">&quot;modify&quot;</span><span class="p">]</span>
<span class="n">target_agent_id</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">rewrite_prompt</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">class</span><span class="w"> </span><span class="nc">ChannelContext</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">microdao_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">visibility</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="s2">&quot;public&quot;</span><span class="p">,</span> <span class="s2">&quot;private&quot;</span><span class="p">,</span> <span class="s2">&quot;microdao&quot;</span><span class="p">]</span>
<span class="n">allowed_agents</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">disabled_agents</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">class</span><span class="w"> </span><span class="nc">FilterContext</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">channel</span><span class="p">:</span> <span class="n">ChannelContext</span>
<span class="n">sender_is_owner</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">sender_is_admin</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">sender_is_member</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">local_time</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</code></pre></div>
<h3 id="12-rules-rulespy">1.2 Rules (rules.py)<a class="headerlink" href="#12-rules-rulespy" title="Permanent link">&para;</a></h3>
<p>Implement:</p>
<div class="codehilite"><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">decide</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="n">MessageCreatedEvent</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="n">FilterContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">FilterDecision</span><span class="p">:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Baseline rules v1:</span>
<span class="sd"> - Якщо event.sender_type == &quot;agent&quot; → decision = &quot;deny&quot; (щоб не було loop).</span>
<span class="sd"> - Якщо channel.visibility == &quot;microdao&quot; і є default assistant агента для microDAO:</span>
<span class="sd"> - target_agent_id = цей агент (поки можна жорстко прописати в config або заглушка).</span>
<span class="sd"> - Якщо час у quiet_hours (23:0007:00 з config.yaml):</span>
<span class="sd"> - decision = &quot;modify&quot;</span>
<span class="sd"> - rewrite_prompt = &quot;Відповідай стисло і тільки якщо запит важливий. Не ініціюй розмову сам.&quot;</span>
<span class="sd"> - Якщо агент заборонений у цьому каналі (agent_id у disabled_agents) → decision = &quot;deny&quot;.</span>
<span class="sd"> - Якщо немає жодного кандидата → decision = &quot;deny&quot;.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">pass</span>
</code></pre></div>
<p><strong>config.yaml:</strong></p>
<div class="codehilite"><pre><span></span><code><span class="nt">nats</span><span class="p">:</span>
<span class="w"> </span><span class="nt">servers</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">[</span><span class="s">&quot;nats://nats:4222&quot;</span><span class="p p-Indicator">]</span>
<span class="w"> </span><span class="nt">messaging_subject</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;messaging.message.created&quot;</span>
<span class="w"> </span><span class="nt">decision_subject</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;agent.filter.decision&quot;</span>
<span class="nt">rules</span><span class="p">:</span>
<span class="w"> </span><span class="nt">quiet_hours</span><span class="p">:</span>
<span class="w"> </span><span class="nt">start</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;23:00&quot;</span>
<span class="w"> </span><span class="nt">end</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;07:00&quot;</span>
<span class="w"> </span><span class="nt">default_agents</span><span class="p">:</span>
<span class="w"> </span><span class="s">&quot;microdao:daarion&quot;</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="s">&quot;agent:sofia&quot;</span>
</code></pre></div>
<h3 id="13-mainpy">1.3 main.py<a class="headerlink" href="#13-mainpy" title="Permanent link">&para;</a></h3>
<ul>
<li>Підняти FastAPI:</li>
<li><code>GET /health</code><code>{ "status": "ok" }</code></li>
<li>
<p><code>POST /internal/agent-filter/test</code> → приймає MessageCreatedEvent, викликає rules.decide(...), повертає FilterDecision.</p>
</li>
<li>
<p>На startup:</p>
</li>
<li>підʼєднатися до NATS</li>
<li>підписатися на subject <code>messaging.message.created</code></li>
</ul>
<p><strong>Алгоритм обробки:</strong>
1. Deserialize payload у MessageCreatedEvent.
2. Підібрати ChannelContext:
- <code>GET /internal/messaging/channels/{channel_id}/context</code> (потрібно додати цей endpoint у messaging-service, якщо ще нема).
- Очікуваний response:
<code>json
{
"microdao_id": "...",
"visibility": "microdao",
"allowed_agents": ["agent:sofia"],
"disabled_agents": []
}</code>
3. Побудувати FilterContext.
4. Викликати rules.decide(event, ctx).
5. Опублікувати FilterDecision у NATS:
- subject: <code>agent.filter.decision</code>
- payload: <code>decision.json()</code></p>
<h3 id="14-dockerfile-readme">1.4 Dockerfile + README<a class="headerlink" href="#14-dockerfile-readme" title="Permanent link">&para;</a></h3>
<ul>
<li>Dockerfile подібний до messaging-service.</li>
<li>README.md:</li>
<li>як запускати локально,</li>
<li>як тестувати <code>/internal/agent-filter/test</code>,</li>
<li>приклад NATS payload.</li>
</ul>
<hr />
<h2 id="2-dagi-router-messaginginbound">2) Розширення DAGI Router під messaging.inbound<a class="headerlink" href="#2-dagi-router-messaginginbound" title="Permanent link">&para;</a></h2>
<p><strong>Goal:</strong>
- DAGI Router має слухати <code>agent.filter.decision</code> і на основі allow-рішень створювати AgentInvocation і штовхати в <code>router.invoke.agent</code>.</p>
<h3 id="21-nats-subscription">2.1 NATS subscription<a class="headerlink" href="#21-nats-subscription" title="Permanent link">&para;</a></h3>
<p>У <code>services/router/</code> (або де реалізований DAGI Router):</p>
<ul>
<li>Підписка на subject: <code>agent.filter.decision</code>.</li>
</ul>
<p><strong>Очікуваний payload:</strong> FilterDecision (див. вище).</p>
<p><strong>Алгоритм:</strong>
- Якщо <code>decision != "allow"</code> → ігноруємо.
- Якщо <code>decision == "allow"</code> і <code>target_agent_id</code> не заданий → логування + ігнор.
- Інакше: побудувати AgentInvocation:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;agent_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;&lt;target_agent_id&gt;&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;entrypoint&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;channel_message&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;payload&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;channel_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;&lt;channel_id&gt;&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;message_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;&lt;message_id&gt;&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;matrix_event_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;&lt;matrix_event_id&gt;&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;microdao_id&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;&lt;microdao_id&gt;&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;rewrite_prompt&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;&lt;rewrite_prompt&gt;&quot;</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>Опублікувати у NATS:
- subject: <code>router.invoke.agent</code>
- payload: AgentInvocation JSON.</p>
<h3 id="22">2.2 Моделі<a class="headerlink" href="#22" title="Permanent link">&para;</a></h3>
<p>Додати в Router:</p>
<div class="codehilite"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">Literal</span>
<span class="k">class</span><span class="w"> </span><span class="nc">AgentInvocation</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">agent_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">entrypoint</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="s2">&quot;channel_message&quot;</span><span class="p">,</span> <span class="s2">&quot;direct&quot;</span><span class="p">,</span> <span class="s2">&quot;cron&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;channel_message&quot;</span>
<span class="n">payload</span><span class="p">:</span> <span class="nb">dict</span>
</code></pre></div>
<h3 id="23">2.3 Конфіг<a class="headerlink" href="#23" title="Permanent link">&para;</a></h3>
<p>Файл <code>router_config.yaml</code> (або аналог):</p>
<div class="codehilite"><pre><span></span><code><span class="nt">messaging_inbound</span><span class="p">:</span>
<span class="w"> </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span>
<span class="w"> </span><span class="nt">source_subject</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;agent.filter.decision&quot;</span>
<span class="w"> </span><span class="nt">target_subject</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;router.invoke.agent&quot;</span>
</code></pre></div>
<h3 id="24-http-debug-endpoint">2.4 HTTP debug endpoint<a class="headerlink" href="#24-http-debug-endpoint" title="Permanent link">&para;</a></h3>
<p>Додати в Router:</p>
<p><strong>POST /internal/router/test-messaging</strong></p>
<p>Body: FilterDecision</p>
<p>Behavior:
- прогнати той самий код, який обробляє NATS event,
- повернути AgentInvocation JSON без публікації у NATS.</p>
<hr />
<h2 id="3-agent-runtime-integration-messenger">3) Agent Runtime integration з Messenger<a class="headerlink" href="#3-agent-runtime-integration-messenger" title="Permanent link">&para;</a></h2>
<p><strong>Goal:</strong>
- Реалізувати agent-runtime-service, який:
- читає контекст каналу (останні повідомлення),
- читає памʼять агента,
- викликає LLM через LLM Proxy,
- постить відповідь у канал через messaging-service.</p>
<p><strong>Create service:</strong> <code>services/agent-runtime/</code></p>
<p><strong>Files:</strong>
- <code>services/agent-runtime/main.py</code>
- <code>services/agent-runtime/models.py</code>
- <code>services/agent-runtime/llm_client.py</code>
- <code>services/agent-runtime/messaging_client.py</code>
- <code>services/agent-runtime/memory_client.py</code>
- <code>services/agent-runtime/config.yaml</code>
- <code>services/agent-runtime/requirements.txt</code>
- <code>services/agent-runtime/Dockerfile</code>
- <code>services/agent-runtime/README.md</code></p>
<h3 id="31-models-modelspy">3.1 Models (models.py)<a class="headerlink" href="#31-models-modelspy" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">Literal</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">datetime</span><span class="w"> </span><span class="kn">import</span> <span class="n">datetime</span>
<span class="k">class</span><span class="w"> </span><span class="nc">AgentInvocation</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">agent_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">entrypoint</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="s2">&quot;channel_message&quot;</span><span class="p">,</span> <span class="s2">&quot;direct&quot;</span><span class="p">,</span> <span class="s2">&quot;cron&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;channel_message&quot;</span>
<span class="n">payload</span><span class="p">:</span> <span class="nb">dict</span>
<span class="k">class</span><span class="w"> </span><span class="nc">ChannelContextMessage</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="n">sender_id</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">sender_type</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="s2">&quot;human&quot;</span><span class="p">,</span> <span class="s2">&quot;agent&quot;</span><span class="p">]</span>
<span class="n">content</span><span class="p">:</span> <span class="nb">str</span>
<span class="n">created_at</span><span class="p">:</span> <span class="n">datetime</span>
</code></pre></div>
<h3 id="32-nats-subscription">3.2 NATS subscription<a class="headerlink" href="#32-nats-subscription" title="Permanent link">&para;</a></h3>
<p><strong>main.py:</strong>
- Підʼєднатись до NATS.
- Підписатися на <code>router.invoke.agent</code>.</p>
<p><strong>Алгоритм:</strong>
1. Deserialize AgentInvocation.
2. Якщо <code>entrypoint != "channel_message"</code> → поки що ігноруємо (або лог).
3. Витягти:
- <code>agent_id</code>
- <code>channel_id</code>, <code>message_id</code>, <code>matrix_event_id</code>, <code>microdao_id</code>, <code>rewrite_prompt</code> з payload.
4. Завантажити blueprint агента:
<code>http
GET /internal/agents/{agent_id}/blueprint</code>
Очікуваний response:
<code>json
{
"id": "...",
"name": "Sofia-Prime",
"model": "gpt-4.1",
"instructions": "System prompt...",
"capabilities": {...}
}</code>
5. Завантажити історію каналу:
<code>http
GET /internal/messaging/channels/{channel_id}/messages?limit=50</code>
→ вернути список повідомлень у форматі ChannelContextMessage.</p>
<ol>
<li>
<p>Витягти останнє human-повідомлення як user input.</p>
</li>
<li>
<p>Запитати памʼять:
<code>http
POST /internal/agent-memory/query
{
"agent_id": "&lt;agent_id&gt;",
"microdao_id": "&lt;microdao_id&gt;",
"channel_id": "&lt;channel_id&gt;",
"query": "&lt;останній текст користувача&gt;"
}</code>
Очікуваний response: список релевантних фрагментів knowledge base.</p>
</li>
<li>
<p>Побудувати промпт для LLM (llm_client.py):</p>
</li>
<li>system: інструкції з blueprint +, якщо є, rewrite_prompt</li>
<li>context: останні N повідомлень (з імʼям, роллю, часом)</li>
<li>memory: релевантні фрагменти</li>
<li>
<p>user: останній текст користувача</p>
</li>
<li>
<p>Викликати LLM через LLM Proxy:
<code>http
POST /internal/llm/proxy
{
"model": "&lt;з blueprint&gt;",
"messages": [ {"role": "...", "content": "..."}, ... ]
}</code>
Очікуваний response:
<code>json
{ "content": "&lt;текст відповіді&gt;" }</code></p>
</li>
<li>
<p>Надіслати відповідь у канал:
<code>http
POST /internal/agents/{agentId}/post-to-channel
{
"channel_id": "&lt;channel_id&gt;",
"text": "&lt;llm response&gt;"
}</code>
Цей endpoint вже повинен існувати у messaging-service (як внутрішній).</p>
</li>
<li>
<p>(optional v1) Записати в памʼять:
<code>http
POST /internal/agent-memory/store
{
"agent_id": "&lt;agent_id&gt;",
"microdao_id": "&lt;microdao_id&gt;",
"channel_id": "&lt;channel_id&gt;",
"content": {
"user_message": "...",
"agent_reply": "..."
}
}</code></p>
</li>
</ol>
<h3 id="33-http-debug-endpoint">3.3 HTTP debug endpoint<a class="headerlink" href="#33-http-debug-endpoint" title="Permanent link">&para;</a></h3>
<p><strong>main.py:</strong></p>
<p><strong>POST /internal/agent-runtime/test-channel</strong></p>
<p>Body: AgentInvocation</p>
<p>Behavior:
- викликає ту саму логіку, що NATS handler,
- але замість реального POST до <code>/internal/agents/{agentId}/post-to-channel</code> просто повертає згенерований текст і зібраний prompt (обережно, без секретів у логах).</p>
<h3 id="34-docker-readme">3.4 Docker + README<a class="headerlink" href="#34-docker-readme" title="Permanent link">&para;</a></h3>
<ul>
<li>Dockerfile за шаблоном інших сервісів.</li>
<li>README.md:</li>
<li>як запускати локально,</li>
<li>як тестувати через <code>/internal/agent-runtime/test-channel</code>,</li>
<li>як дивитись NATS events.</li>
</ul>
<hr />
<h2 id="4-docker-compose">4) Інтеграція в docker-compose та документацію<a class="headerlink" href="#4-docker-compose" title="Permanent link">&para;</a></h2>
<h3 id="41-docker-compose">4.1 docker-compose<a class="headerlink" href="#41-docker-compose" title="Permanent link">&para;</a></h3>
<ul>
<li>Додати <code>agent-filter</code>, <code>router</code> (якщо ще не доданий), <code>agent-runtime</code> у загальний docker-compose (або створити окремий <code>docker-compose.agents.yml</code>).</li>
<li>Забезпечити доступ до:</li>
<li>NATS</li>
<li>messaging-service</li>
<li>agent-memory-service (якщо вже існує) / stub</li>
<li>agents-service (blueprints) / stub</li>
<li>llm-proxy-service / stub</li>
</ul>
<h3 id="42">4.2 Документація<a class="headerlink" href="#42" title="Permanent link">&para;</a></h3>
<p>Оновити/додати:
- <code>docs/MESSAGING_ARCHITECTURE.md</code> — помітити, що PHASE 2 реалізовано.
- <code>docs/MESSENGER_COMPLETE_SPECIFICATION.md</code> — додати розділ "Agent Integration (PHASE 2)" з посиланнями на нові сервіси.
- При потребі: окремий <code>docs/AGENT_INTEGRATION_PHASE2.md</code> з коротким описом flow.</p>
<hr />
<h2 id="acceptance-criteria">Acceptance Criteria<a class="headerlink" href="#acceptance-criteria" title="Permanent link">&para;</a></h2>
<ul>
<li>✅ Human → пише в канал → agent_filter приймає event → DAGI Router відправляє AgentInvocation → Agent Runtime читає історію й памʼять → агент відповідає в той самий канал → повідомлення відображається у /messenger і в Element.</li>
<li>✅ Є мінімум один робочий агент (наприклад, Sofia-Prime), який стабільно відповідає в одному каналі microDAO.</li>
<li>Всі сервіси стартують через docker-compose, health-checkи зелені.</li>
</ul>
<hr />
<p><strong>Version:</strong> 1.0.0<br />
<strong>Date:</strong> 2025-11-24<br />
<strong>Priority:</strong> High<br />
<strong>Estimated Time:</strong> 4 weeks</p>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "../..", "features": ["navigation.sections", "navigation.instant", "content.code.copy"], "search": "../../assets/javascripts/workers/search.b8dbb3d2.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
<script src="../../assets/javascripts/bundle.3220b9d7.min.js"></script>
</body>
</html>