Adds POST /api/audit/internal authenticated via X-Internal-Service-Token header
(SOFIIA_INTERNAL_TOKEN env). Allows matrix-bridge-dagi and other internal services
to write audit events without team keys. Reuses existing audit_log() + db layer.
Made-with: Cursor
- adds Development Team section with Сергій Миколайович Пліс (@vetr369)
as Hardware Engineer & Infrastructure Specialist for DAGI nodes
- grants developer-level access to technical node/infra information
Made-with: Cursor
- adds runbook_artifacts.py: server-side render of release_evidence.md and
post_review.md from DB step results (no shell); saves to
SOFIIA_DATA_DIR/release_artifacts/<run_id>/
- evidence: auto-fills preflight/smoke/script outcomes, step table, timestamps
- post_review: auto-fills metadata, smoke results, incidents from step statuses;
leaves [TODO] markers for manual observation sections
- adds POST /api/runbooks/runs/{run_id}/evidence and /post_review endpoints
- updates runbook_runs.evidence_path in DB after render
- adds 11 tests covering file creation, key sections, TODO markers, 404s, API
Made-with: Cursor
GET /api/runbooks/status returns docs_root, indexed_files, indexed_chunks, last_indexed_at, fts_available; docs_index_meta table and set on rebuild
Made-with: Cursor
includes preflight, restart, smoke, observation, evidence steps
defines success criteria and metrics to collect for next-step decision
Made-with: Cursor
Version cursor payloads and keep backward compatibility while adding dedicated tie-breaker regression coverage for equal timestamps to prevent pagination duplicates and gaps.
Made-with: Cursor
Move idempotency TTL/LRU logic into a dedicated store module with a swap-ready interface and wire chat send flow to use store get/set semantics without changing API behavior.
Made-with: Cursor
Add regression coverage for router URL resolution when NODE_ID is unset and ROUTER_URL is present, and verify explicit NODES_NODA2_ROUTER_URL keeps higher priority.
Made-with: Cursor
Expose Prometheus-style metrics endpoint and add counters for send requests, idempotency replays, and cursor pagination calls, including a safe in-process fallback exposition when prometheus_client is unavailable.
Made-with: Cursor
Add BFF runtime support for chat idempotency (header priority over body) with bounded in-memory TTL/LRU replay cache, implement cursor-based pagination for chats and messages, and add a safe NODA2 local router fallback for legacy runs without NODE_ID.
Made-with: Cursor
Add focused API contract tests for chat idempotency, cursor pagination, and node routing behavior using isolated local fixtures and mocked upstream inference.
Made-with: Cursor
- Node Worker: replace swapper_vision with ollama_vision (direct Ollama API)
- Node Worker: add NATS subjects for stt/tts/image (stubs ready)
- Node Worker: remove SWAPPER_URL dependency from config
- Router: vision calls go directly to Ollama /api/generate with images
- Router: local LLM calls go directly to Ollama /api/generate
- Router: add OLLAMA_URL and PREFER_NODE_WORKER=true feature flag
- Router: /v1/models now uses NCS global capabilities pool
- NCS: SWAPPER_URL="" -> skip Swapper probing (status=disabled)
- Swapper configs: remove all hardcoded model lists, keep only runtime
URLs, timeouts, limits
- docker-compose.node1.yml: add OLLAMA_URL, PREFER_NODE_WORKER for router;
SWAPPER_URL= for NCS; remove swapper-service from node-worker depends_on
- docker-compose.node2-sofiia.yml: same changes for NODA2
Swapper service still runs but is NOT in the critical inference path.
Source of truth for models is now NCS -> Ollama /api/tags.
Made-with: Cursor
NATS wildcards (node.*.capabilities.get) only work for subscriptions,
not for publish. Switch to a dedicated broadcast subject
(fabric.capabilities.discover) that all NCS instances subscribe to,
enabling proper scatter-gather discovery across nodes.
Made-with: Cursor
Architecture for 150+ nodes:
- global_capabilities_client.py: NATS scatter-gather discovery using
wildcard subject node.*.capabilities.get — zero static node lists.
New nodes auto-register by deploying NCS and subscribing to NATS.
Dead nodes expire from cache after 3x TTL automatically.
Multi-node model_select.py:
- ModelSelection now includes node, local, via_nats fields
- select_best_model prefers local candidates, then remote
- Prefer list resolution: local first, remote second
- All logged per request: node, runtime, model, local/remote
NODA1 compose:
- Added node-capabilities service (NCS) to docker-compose.node1.yml
- NATS subscription: node.noda1.capabilities.get
- Router env: NODE_CAPABILITIES_URL + ENABLE_GLOBAL_CAPS_NATS=true
NODA2 compose:
- Router env: ENABLE_GLOBAL_CAPS_NATS=true
Router main.py:
- Startup: initializes global_capabilities_client (NATS connect + first
discovery). Falls back to local-only capabilities_client if unavailable.
- /infer: uses get_global_capabilities() for cross-node model pool
- Offload support: send_offload_request(node_id, type, payload) via NATS
Verified on NODA2:
- Global caps: 1 node, 14 models (NODA1 not yet deployed)
- Sofiia: cloud_grok → grok-4-1-fast-reasoning (OK)
- Helion: NCS → qwen3:14b local (OK)
- When NODA1 deploys NCS, its models appear automatically via NATS discovery
Made-with: Cursor
Router model selection:
- New model_select.py: resolve_effective_profile → profile_requirements →
select_best_model pipeline. NCS-first with graceful static fallback.
- selection_policies in router-config.node2.yml define prefer order per
profile without hardcoding models (e.g. local_default_coder prefers
qwen3:14b then qwen3.5:35b-a3b).
- Cloud profiles (cloud_grok, cloud_deepseek) skip NCS; on cloud failure
use fallback_profile via NCS for local selection.
- Structured logs: selected_profile, required_type, runtime, model,
caps_age_s, fallback_reason on every infer request.
Grok model fix:
- grok-2-1212 no longer exists on xAI API → updated to
grok-4-1-fast-reasoning across all 3 hardcoded locations in main.py
and router-config.node2.yml.
NCS NATS request/reply:
- node-capabilities subscribes to node.noda2.capabilities.get (NATS
request/reply). Enabled via ENABLE_NATS_CAPS=true in compose.
- NODA1 router can query NODA2 capabilities over NATS leafnode without
HTTP connectivity.
Verified:
- NCS: 14 served models from Ollama+Swapper+llama-server
- NATS: request/reply returns full capabilities JSON
- Sofiia: cloud_grok → grok-4-1-fast-reasoning (tested, 200 OK)
- Helion: NCS → qwen3:14b via Ollama (caps_age=23.7s cache hit)
- Router health: ok
Made-with: Cursor
Bug fixes:
- Bug A: GROK_API_KEY env mismatch — router expected GROK_API_KEY but only
XAI_API_KEY was present. Added GROK_API_KEY=${XAI_API_KEY} alias in compose.
- Bug B: 'grok' profile missing in router-config.node2.yml — added cloud_grok
profile (provider: grok, model: grok-2-1212). Sofiia now has
default_llm=cloud_grok with fallback_llm=local_default_coder.
- Bug C: Router silently defaulted to cloud DeepSeek when profile was unknown.
Now falls back to agent.fallback_llm or local_default_coder with WARNING log.
Hardcoded Ollama URL (172.18.0.1) replaced with config-driven base_url.
New service: Node Capabilities Service (NCS)
- services/node-capabilities/ — FastAPI microservice exposing live model
inventory from Ollama, Swapper, and llama-server.
- GET /capabilities — canonical JSON with served_models[] and inventory_only[]
- GET /capabilities/models — flat list of served models
- POST /capabilities/refresh — force cache refresh
- Cache TTL 15s, bound to 127.0.0.1:8099
- services/router/capabilities_client.py — async client with TTL cache
Artifacts:
- ops/node2_models_audit.md — 3-layer model view (served/disk/cloud)
- ops/node2_models_audit.yml — machine-readable audit
- ops/node2_capabilities_example.json — sample NCS output (14 served models)
Made-with: Cursor
Clarify Helion group behavior: stay silent unless energy topic or direct mention, but answer operational questions when directly addressed.
Co-authored-by: Cursor <cursoragent@cursor.com>
Prevent DeepSeek DSML from leaking to users and avoid returning raw memory_search/web results when DSML is detected.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Fix market-data-service host port 8891→8893 (conflict with Swapper)
- Increase healthcheck start_period/retries for market-data-service
- Add Market Data Service + SenpAI MD Consumer to PROJECT-MASTER-INDEX.md
- Update noda1-operations rule and skill with new ports/containers
Co-authored-by: Cursor <cursoragent@cursor.com>
Router (main.py):
- When DSML detected in 2nd LLM response after tool execution,
make a 3rd LLM call with explicit synthesis prompt instead of
returning raw tool results to the user
- Falls back to format_tool_calls_for_response only if 3rd call fails
Router (tool_manager.py):
- Added _strip_think_tags() helper for <think>...</think> removal
from DeepSeek reasoning artifacts
Gateway (http_api.py):
- Strip <think>...</think> tags before sending to Telegram
- Strip DSML/XML-like markup (function_calls, invoke, parameter tags)
- Ensure empty text after stripping gets "..." fallback
Deployed to NODE1 and verified services running.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Fixed unquoted `helion` variable reference to string literal `"helion"`
in tool_manager.py search_memories fallback
- Replaced `[Контекст пам'яті]` with `[INTERNAL MEMORY - do NOT repeat
to user]` in all 3 injection points in main.py
- Verified: Senpai now responds without Helion contamination or memory
brief leaking
Tested and deployed on NODE1.
Co-authored-by: Cursor <cursoragent@cursor.com>
1. YAML structure bug: Senpai was in `policies:` instead of `agents:`
in router-config.yml. Router couldn't find Senpai config → no routing
rule → fallback to local model.
2. tool_manager agent_id not passed: memory_search and graph_query
tools were called without agent_id → defaulted to "helion" →
ALL agents' tool calls searched Helion's Qdrant collections.
Fixed: agent_id now flows from main.py → execute_tool → _memory_search.
3. Config not mounted: router-config.yml was baked into Docker image,
host changes had no effect. Added volume mount in docker-compose.
Also added:
- Sofiia agent config + routing rule (was completely missing)
- Senpai routing rule: cloud_deepseek (was falling to local qwen3:8b)
- Anti-echo instruction for memory brief injection
Deployed and verified on NODE1: Senpai now searches senpai_* collections.
Co-authored-by: Cursor <cursoragent@cursor.com>
Brand commands (~290 lines):
- Code was trapped inside `if reply_to_message:` block (unreachable)
- Moved to feature flag: ENABLE_BRAND_COMMANDS=true to activate
- Zero re-indentation: 8sp code naturally fits as feature flag body
- Helper functions (_brand_*, _artifact_*) unchanged
Memory LLM Summary:
- Replace placeholder with real DeepSeek API integration
- Structured output: summary, goals, decisions, open_questions, next_steps, key_facts
- Graceful fallback if API key not set or call fails
- Added MEMORY_DEEPSEEK_API_KEY config
- Ukrainian output language
Deployed and verified on NODE1.
Co-authored-by: Cursor <cursoragent@cursor.com>
1. thread_has_agent_participation (SOWA Priority 11):
- New function has_agent_chat_participation() in behavior_policy.py
- Checks if agent responded to ANY user in this chat within 30min
- When active + user asks question/imperative → agent responds
- Different from per-user conversation_context (Priority 12)
- Wired into both detect_explicit_request() and analyze_message()
2. ACK reply_to_message_id:
- When SOWA sends ACK ("NUTRA тут"), it now replies to the user's
message instead of sending a standalone message
- Better UX: visually linked to what the user wrote
- Uses allow_sending_without_reply=True for safety
Known issue (not fixed - too risky):
- Lines 1368-1639 in http_api.py are dead code (brand commands /бренд)
at incorrect indentation level (8 spaces, inside unreachable block)
- These commands never worked on NODE1, fixing 260 lines of indentation
carries regression risk — deferred to separate cleanup PR
Co-authored-by: Cursor <cursoragent@cursor.com>
When a user replies to an agent's message in Telegram groups,
it is now treated as a direct mention (SOWA FULL response).
Implementation:
- Detect reply_to_message.from.is_bot in Gateway webhook handler
- Verify bot_id matches this agent's token (multi-agent safe)
- Pass is_reply_to_agent=True to detect_explicit_request() and
analyze_message() (SOWA v2.2)
- Add is_reply_to_agent to Router metadata for analytics
SOWA already had Priority 3 logic for reply_to_agent → FULL,
it was just never wired up (had TODO placeholders with False).
Edge cases handled:
- Only triggers when reply is to THIS agent's bot (not other bots)
- Reply to forwarded messages: won't trigger (from.is_bot would be
the original sender, not the bot)
- Works alongside existing DM, mention, and training group rules
Co-authored-by: Cursor <cursoragent@cursor.com>
CI:
- python-services-ci now only runs on main branch (not feature branches)
- Install deps with lock fallback (if lock file is stale, install without it)
Cursor rules:
- New project-context.mdc (alwaysApply: true) — gives AI full project
context immediately in every new chat
- Updated noda1-operations.mdc: alwaysApply: true, fixed container names
(dagi-router-node1, not dagi-staging-router)
This ensures that when opening a new Cursor chat in this workspace,
the AI already knows: project structure, NODE1 server details, all 13
agents, SSH credentials location, and key documentation paths.
Co-authored-by: Cursor <cursoragent@cursor.com>
SOWA fixes:
- Add Russian variants for all agents (сэнпай, хелион, друид, etc.)
- Add missing sofiia agent to AGENT_NAME_VARIANTS
- Add /senpai, /sofiia command prefixes
Vision denial fix (all 13 agents):
- Add explicit rule: "Never say you can't see/analyze images"
- Agents have Vision API via Swapper (qwen3-vl-8b)
- When vision model describes a photo, the follow-up text model (DeepSeek)
must not deny having seen it
Root cause: NUTRA correctly analyzed a photo via vision model, but when
asked a follow-up question, DeepSeek (text model) responded "I cannot
see images" because the system prompt lacked the denial prevention rule.
Co-authored-by: Cursor <cursoragent@cursor.com>
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>
NODA1 agents now:
- Don't respond to broadcasts/posters/announcements without direct mention
- Don't respond to media (photo/link) without explicit question
- Keep responses short (1-2 sentences by default)
- No emoji, no "ready to help", no self-promotion
Added:
- behavior_policy.py: detect_directed_to_agent(), detect_broadcast_intent(), should_respond()
- behavior_policy_v1.txt: unified policy block for all prompts
- Pre-LLM check in http_api.py: skip Router call if should_respond=False
- NO_OUTPUT handling: don't send to Telegram if LLM returns empty
- Updated all 9 agent prompts with Behavior Policy v1
- Unit and E2E tests for 5 acceptance cases
- Added TRAINING_GROUP_IDS constant for Agent Preschool group
- Gateway now adds "[РЕЖИМ НАВЧАННЯ]" prefix for training groups
- Agents will respond to all messages in training groups
Co-authored-by: Cursor <cursoragent@cursor.com>
All agents now respond to all messages in the training group
"Agent Preschool Daarion.city" without requiring mentions.
Updated prompts: helion, daarwizz, greenfood, nutra, agromatrix, druid
Co-authored-by: Cursor <cursoragent@cursor.com>
Helion now only responds in groups when:
- Mentioned by name/username
- Direct question about Energy Union
- Previously was responding to all messages in groups
Co-authored-by: Cursor <cursoragent@cursor.com>
- Use stdlib urllib.request instead of requests library
- requests was not installed in the router image, causing healthcheck
to always fail with "ModuleNotFoundError: No module named 'requests'"
- Increase start_period to 30s and retries to 5 for stability
Co-authored-by: Cursor <cursoragent@cursor.com>
- docker-compose.node1.yml: Add network aliases (router, gateway,
memory-service, qdrant, nats, neo4j) to eliminate manual
`docker network connect --alias` commands
- docker-compose.node1.yml: ROUTER_URL now uses env variable with
fallback: ${ROUTER_URL:-http://router:8000}
- docker-compose.node1.yml: Increase router healthcheck start_period
to 30s and retries to 5
- .gitignore: Add noda1-credentials.local.mdc (local-only SSH creds)
- scripts/node1/verify_agents.sh: Improved output with agent list
- docs: Add NODA1-AGENT-VERIFICATION.md, NODA1-AGENT-ARCHITECTURE.md,
NODA1-VERIFICATION-REPORT-2026-02-03.md
- config/README.md: How to add new agents
- .cursor/rules/, .cursor/skills/: NODA1 operations skill for Cursor
Root cause fixed: Gateway could not resolve 'router' DNS name when
Router container was named 'dagi-staging-router' without alias.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Коли підключати агентів: після налаштування інфраструктури
- DAGI Router: готово до deployment на NODE1/NODE3
- Swapper Service: готово до deployment на NODE1/NODE3
- Логування: все записується (GitHub, Gitea, GitLab)
- NODE1 перевірка: чистий, інцидентів не виявлено
Рекомендований порядок дій включено.
- K8s deployment для DAGI Router (NODE1)
- K8s deployment для Swapper Service (NODE1)
- ConfigMaps для конфігурацій
- Services (ClusterIP + NodePort)
- Інтеграція з NATS JetStream
- Оновлено DEPLOYMENT-PLAN.md з конкретними інструкціями
TODO: Створити аналоги для NODE3
- Відповіді на питання про підключення агентів
- План встановлення DAGI Router на NODE1/NODE3
- План встановлення Swapper Service на NODE1/NODE3
- Перевірка логування (GitLab, Gitea, GitHub)
- Перевірка NODE1 на інциденти (чистий)
Статус:
- DAGI Router: працює на NODE2, потрібно на NODE1/NODE3
- Swapper Service: працює на NODE2, потрібно на NODE1/NODE3
- Агенти: підключати після налаштування інфраструктури
- Atomic генерація всіх секретів (generate-all-secrets.sh)
- Auth enforcement перевірка (enforce-auth.sh)
- Оновлений full flow test (must-pass)
- Prometheus alerting rules для Memory Module
- Matrix alerts bridge (алерти в ops room)
- Policy engine документація для пам'яті
Готово до production deployment!
- JWT middleware для FastAPI
- Генерація/перевірка JWT токенів
- Скрипти для генерації Qdrant API keys
- Скрипти для генерації NATS operator JWT
- План реалізації Auth
TODO: Додати JWT до endpoints, NATS nkeys config, Qdrant API key config
- Автоматичне створення streams при старті worker
- Перевірка наявності streams перед створенням
- Підтримка всіх 4 streams (MM_ONLINE, MM_OFFLINE, MM_WRITE, MM_EVENTS)
Це вирішує проблему з DNS в K8s Job
- Matrix Client (підключення та синхронізація)
- RBAC Checker (перевірка прав через Postgres)
- Job Creator (створення jobs з команд)
- NATS Publisher (публікація jobs у streams)
- K8s deployment
- README з документацією
Команди: !embed, !retrieve, !summarize
TODO: Реальна інтеграція з Matrix homeserver, статуси результатів
- NATS працює в standalone режимі (1 replica)
- Виправлено server_name через initContainer
- Створено K8s Job для створення streams (через Python)
- Створено create-streams.py скрипт
TODO: Streams створити через worker-daemon або після виправлення DNS в Job