Frontier Intelligence
Provides monitoring and observability capabilities through Grafana dashboards for runtime metrics and system health visualization.
Ingests content from Habr as a source for trend analysis and knowledge extraction, processing articles through the enrichment pipeline.
Integrates LangChain for orchestration of enrichment tasks including relevance filtering, concept extraction, and workspace routing.
Stores and queries concept graphs with workspace isolation, enabling graph-based knowledge representation and relationship analysis.
Uses OpenAI SDK for LLM integration via gpt2giga-proxy, powering relevance scoring, concept extraction, and semantic analysis.
Serves as canonical storage for raw data, metadata, and trend clusters with workspace isolation across all major tables.
Collects and exposes runtime metrics for monitoring system performance and operational health.
Provides stream-based messaging for ingest pipelines and supports caching and queue management for processing tasks.
Ingests content from RSS/Atom feeds as source material for trend detection and knowledge base population.
Performs gap analysis to identify missing signals by comparing expected topic distributions against actual content.
Ingests content from Telegram channels using Telethon with account rotation and proxy support for data collection.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Frontier Intelligencesummarize recent AI trends for my research project"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Frontier Intelligence
Переписан 2026-08-04 против работающего стека. Аудит нашёл в прежней редакции 35 расхождений — неверные схемы таблиц, мёртвые имена коллекций Qdrant, несуществующие метки и связи Neo4j, выключенные xray-профили, несуществующие профили compose, реактивный пайплайн без единой строки продюсера. Разделы про БД, Qdrant, Neo4j, Current Runtime, Workspace, MCP Tools, Processing Pipeline, Admin UI, статус, архитектуру, профили compose и структуру репозитория переписаны по коду и проверены на живом сервере.
Там, где прежний текст врал, стоит пометка-предупреждение — чтобы читатель, помнящий старую версию, понимал, что изменилось. Метод и полный разбор: AUDIT-2026-08-04.md.
Принцип, принятый при переписывании: схемы БД, деревья каталогов и списки файлов здесь больше не дублируются — только назначение плюс указание, где смотреть источник правды и какой командой свериться. Копия расходится с оригиналом всегда, вопрос лишь в сроке.
Recent Docs
Аудит документации и стека 2026-08-04 — что чему не соответствовало, маршрут работ (заходы 0–5) и журнал принятых решений: AUDIT-2026-08-04.md. Разделы этого файла про БД, Qdrant, Neo4j, xray, профили compose, MCP-инструменты, Trend Detection, Admin UI и структуру репозитория переписаны по коду в тот же день.
Незаконченный функционал — 52 пункта с доказательствами: TODO-UNFINISHED.md.
RSI — рекурсивное самоулучшение детектора (Фаза 0 + контуры A/B/C/D/D+, связка B→A, человек-гейты, кроны, MCP-инструменты, метрики/алерты): rsi.md (2026-06-26).
Rollout summary for the multi-connector production upgrade: CHANGELOG-2026-03-28.md
Source connector model, runtime tables, proxy rules, and live starter bundle: source-connectors-runbook.md
Server-side operational notes and common container issues: ops-server-troubleshooting.md
End-to-end pipeline verification checklist: pipeline-e2e-checklist.md
Security and first-git preflight: security-git-preflight.md
Server-first git workflow: server-git-workflow.md
Remaining operational follow-ups for the multi-LLM rollout: TODO_MULTI_LLM_ROLLOUT.md
Единый аудит оркестратора LLM (as-is/to-be, best practices, roadmap): llm-orchestrator-audit.md
Operational runbook по классам LLM-alerts (trigger/diagnosis/action/rollback): runbooks/llm-orchestrator-alerts.md
SLI/SLO для text/vision/embeddings и error-budget эскалации: sre/llm-orchestrator-sli-slo.md
Шаблон еженедельного audit snapshot (включая delivery и post-deploy verification): audit/llm-orchestrator-weekly-snapshot.md
Обязательный Context7-gate для policy/routing изменений: llm-orchestrator-context7-gate.md
Runtime mode overlays: runtime-modes.md
Workspace hygiene and temporary-file policy live in: server-git-workflow.md
Related MCP server: Faulkner DB
Current Runtime
LLM —
openaiSDK напрямую (openai==1.54.4). Ни LangChain, ни gigachain в проекте нет и вводить их не планируется:worker/gigachat_client.py:1прямо документирует отказ. Маршрутизация — собственный control plane (shared/llm_routing.py,shared/llm_control_plane.py) поверх четырёх провайдеров: wormsoft, polza, openrouter, gigachat.Живая таблица маршрутов —
admin_runtime_settings.llm_routing_v1, hot-reload без пересборки. ЗначенияGIGACHAT_MODEL_*из.env— только основа, поверх которой работает роутер; читать их как исчерпывающее описание продакшн-маршрутизации нельзя.GigaChat больше не обслуживает основную цепочку обогащения.
relevance,concepts,valenceушли на wormsoft (фолбэки — polza и gigachat). За GigaChat остались эмбеддинги (EmbeddingsGigaR, режим strict) иmcp_synthesis. Ходит он черезgpt2giga-proxyна 8090.GigaChat-2-Liteне использовать в рантайме, пока он не появится вGET /v1/modelsна активном прокси.X-Session-IDи/tokens/count— best-effort оптимизации. Если пара SDK/прокси их отвергает, конвейер обязан продолжать работать без кэша и подсчёта токенов.**
INDEXING_MAX_CONCURRENCY=4.**Значение поднимали 1 → 2 → 4 в июле, расшивая пропускную способность enrichment. Прежняя рекомендация «держать 1 на этом контуре» недействительна.Egress наружу только через xray. Прямого выхода в интернет у хоста нет:
api.telegram.orgс хоста отвечаетNetwork is unreachable, работает socks5xray:10808— и это имя резолвится только внутри docker-сети.
XRAY: реестр профилей
Продакшн использует реестр профилей, а не единственный XRAY_VLESS_*. Файлы на сервере:
/opt/frontier-intelligence/runtime/xray-profiles.json
/opt/frontier-intelligence/runtime/xray-active-profile.txt
/opt/frontier-intelligence/runtime/xray-previous-profile.txt
/opt/frontier-intelligence/runtime/xray-reload.triggerПорядок отказоустойчивости здесь намеренно не перечислен. Он меняется при каждом переключении, и зафиксированный в документе список немедленно устаревает: на 04.08.2026 все три профиля, перечисленные в прежней редакции как активная цепочка, были выключены, а работал не упомянутый в ней вовсе
profile_e_tls_tm_ws. Смотреть текущее состояние надо в реестре:ssh frontier-intelligence \ "python3 -c \"import json;[print(p['name'], p['enabled'], p['priority']) \ for p in json.load(open('/opt/frontier-intelligence/runtime/xray-profiles.json'))['profiles']]\""Выбирается включённый профиль с наименьшим
priority.
Быстрая проверка HTTP (с хоста)
Единого API на :8000 в текущем compose нет.
curl -sS http://127.0.0.1:8100/healthz # MCP
curl -sS http://127.0.0.1:8101/api/health # Admin
curl -sS http://127.0.0.1:8100/metrics/ # метрики MCP; БЕЗ слэша отдаёт 307Прочее: PaddleOCR http://127.0.0.1:8008/readyz, Prometheus 9090, Alertmanager 9093,
Grafana 3000, Qdrant 6333, Neo4j 7474.
401на/api/*— это норма, а не поломка. Все эндпоинты admin, кроме/api/healthи/api/auth/login, закрыты cookie/Basic-авторизацией. Прежняя редакция предлагала блок командcurlпо/api/monitoring/xray/*как рабочую процедуру — в написанном виде он не выполняется ни одной строкой. Управлять профилями xray и запускать проверки нужно из авторизованной сессии админки либо с явными учётными данными.
Управление xray через Admin API
Все вызовы требуют авторизации (см. выше). Эндпоинты:
GET /api/monitoring/xray/health текущее здоровье
POST /api/monitoring/xray/health/run прогнать проверку сейчас
GET /api/monitoring/xray/health/history история
GET /api/monitoring/xray/profiles реестр профилей
GET /api/monitoring/xray/remediation/history история переключений
POST /api/monitoring/xray/remediate/switch переключить на профиль
POST /api/monitoring/xray/remediate/rollback вернуть предыдущийЗдоровье двухслойное: transport — общие пробы через socks5://xray:10808,
source_smoke — реальные URL источников, которые важны для приёма. Автоматическое
переключение локально для admin и срабатывает только после настроенной серии
деградаций и с учётом cooldown.
Urgent Trend Alerts
Daily digests are intentionally disabled. The admin scheduler only sends Telegram alerts for rare, confirmed stable trend spikes:
ADMIN_TREND_ALERT_CRON=25 * * * *checks once per hour.TREND_ALERT_MIN_SIGNAL_SCORE=0.80,TREND_ALERT_MIN_DOC_COUNT=5,TREND_ALERT_MIN_SOURCE_COUNT=3select strong confirmed clusters.TREND_ALERT_MAX_PER_7D=2caps delivery to roughly 0-2 urgent messages per week.trend_alertsin PostgreSQL stores sent alerts and deduplicates byworkspace_id + cluster_key + alert_kind.Manual check:
POST http://127.0.0.1:8101/api/pipeline/run-urgent-trend-alerts?dry_run=true— требует авторизации, голыйcurlвернёт401 {"detail":"unauthorized"}.
С 04.08.2026 у алертинга два независимых пути доставки: штатный вебхук в admin
и прямой receiver telegram-direct в Alertmanager, который ходит в Telegram сам.
Критические правила идут обоими сразу, поэтому падение admin больше не глушит
сообщение о падении admin. Плюс dead man's switch: правило FrontierWatchdog
и внешний наблюдатель scripts/alert-watchdog.sh в cron.
Персональная система мониторинга и синтеза трендов.
Собирает сигналы из множества источников, обогащает через собственный мульти-провайдерный
LLM-роутер (wormsoft / polza / openrouter / GigaChat), хранит в векторной базе и графе знаний,
отдаёт через MCP-шлюз в Claude Code и Claude Desktop.
Один сервис — несколько рабочих пространств (workspace).
Клиент подключается к одному MCP-шлюзу, но работает со своим изолированным срезом данных.
Настройки — источники, категории, веса, расписания — правятся через Admin UI, с оговоркой про bootstrap из YAML (см. раздел про Admin UI).
Концепция Workspace
Один стек, шесть изолированных пространств. Источник правды — config/workspaces.yml
(и таблица workspaces в БД, куда его накатывает bootstrap).
workspace | Тематика | Потребитель |
| automotive, HMI, future mobility, EV | Claude Project: visionary-designer |
| LLM, агенты, AI-инструменты, инференс | внешний MCP-клиент ai-researcher (опционально) |
| исследовательский контур AI | — |
| AI-продукты и медиа | — |
| дизайн-системы, UX-паттерны, визуальная культура | внешний MCP-клиент design-director (опционально) |
| автомобильный HMI (заведён 03.08.2026) | наполняется: включён 1 источник из 10 |
Прежняя редакция перечисляла три пространства и называла потребителя «Codex Project». И то и другое неверно: пространств шесть, а MCP отдаётся HTTP-шлюзом на
:8102и прописан в Claude Code и Claude Desktop.
Изоляция на уровне данных, а не инфраструктуры:
каждый документ, тренд и концепт помечен
workspace_id;Qdrant — одна коллекция на модель эмбеддингов, изоляция payload-фильтром по
workspace_idпри каждом запросе (не отдельная коллекция на пространство);Neo4j:
(:Workspace)— узел-корень пространства;PostgreSQL:
workspace_id TEXT NOT NULLво всех основных таблицах.
Cross-workspace сигналы. Поле workspaces.cross_workspace_bridges заполнено у пяти
пространств из шести, пишется тремя путями и редактируется в админке.
Но потребителя у него нет. Ни один поисковый инструмент мостами выдачу не расширяет:
list_workspacesиget_workspace_overviewтолько показывают значение,get_frontier_briefберёт строго переданный список пространств. Утверждение «тренд изai_trendsможет попасть вdisruption, если релевантен обоим» описывает замысел, а не поведение. Вauto_hmiполе сознательно оставлено пустым с комментарием «их не читает никто».
Статус реализации
Снимок на 04.08.2026, сверен с работающим стеком. Столбец «Предел» обязателен: почти всё в этой таблице работает не «вообще», а до конкретной границы, и именно граница устаревает первой. Числа по корпусу (сколько постов, кластеров, источников) намеренно не переносятся в таблицу — они меняются ежедневно; под таблицей лежат команды, которыми любое такое утверждение пересчитывается за минуту.
Компонент | Состояние | Предел — где именно кончается «работает» |
Core infra: postgres, redis, qdrant, neo4j, gpt2giga-proxy | Работает | Профиль |
Ingest: | Работает | Живых типов четыре. |
Ingest: ротация Telegram-аккаунтов | Работает частично | Слотов два ( |
Worker: relevance → concepts → valence → embeddings → Qdrant | Работает | Hybrid dense 2560d + BM25 sparse (fastembed). Коллекция одна на модель эмбеддингов, изоляция воркспейсов — payload-фильтром, не отдельными коллекциями |
Worker: Neo4j concept graph | Работает на запись | Инлайн- |
Worker: vision (S3, GigaChat Vision, PaddleOCR) | Работает на телеграм-корпусе |
|
Semantic clusters | Кластеры есть во всех шести воркспейсах | Наполнение несопоставимое: от десятков тысяч в |
Stable trend clusters | Работает, но наполняется практически только | В четырёх остальных воркспейсах — единицы кластеров (2–6 на каждый), у |
Emerging signals | Работает | Считаются в том же прогоне |
Missing signals | Работает по расписанию | Gap-анализ вызывается внутри |
Urgent trend alerts | Работает | Только Telegram, только подтверждённые всплески stable-трендов, cron |
Admin UI: workspaces, sources, pipeline, search, clusters, graph, media | Работает | 11 роутеров в |
MCP: поиск, наблюдаемость, кластеры, здоровье источников, | Работает | REST-слой на |
Prometheus + Grafana | Работает | Два дашборда: |
crawl4ai, SearXNG, PaddleOCR, xray | Работают как вспомогательные | crawl4ai читает |
Четыре утверждения прошлой версии были неверны — если они помнятся, это не аберрация памяти:
habrне тип источника.shared/source_definitions.py:12держит его какdeprecated alias kept for compatibilityи канонизирует вrss;ingest/sources/habr_source.py— класс без тела (class HabrSource(RSSSource): pass); строк сsource_type='habr'в БД ноль. Зато в прежнем списке отсутствовал реально работающий типapi— на нём висят коннекторы Hacker News, дающие второй по объёму корпус после rss.
Шлюз на
:8102— не SSE.mcp/mcp_gateway.py:1— «Streamable HTTP транспорт», запускmcp.run(transport="streamable-http"), эндпоинт/mcp. Клиент, настроенный на SSE, не подключится.
Vision обслуживает малую долю корпуса.
media_urlsв событие кладёт толькоingest/sources/telegram_source.py; у rss, web и api постов сhas_media— ноль. На 04.08.2026 vision пройден примерно у 1,6% постов, и это потолок конструкции, а не отставание очереди.
Стабильные тренды — это про
disruption. Из 393 строкtrend_clusters376 лежат вdisruption; вdesignпоследний кластер задетектирован 14.07, вai_trends— 28.07. Утверждение «работает» верно для механики, но не для наполнения остальных пространств.
Чем сверить, не полагаясь на этот текст. Все запросы читающие:
ssh frontier-intelligence
cd /opt/frontier-intelligence
export COMPOSE_PROFILES="core,ingest,xray,worker,crawl,paddleocr,mcp,admin,searxng,monitor"
# какие типы источников живы и сколько включено
docker compose exec -T postgres psql -U frontier -d frontier -c \
"SELECT source_type, count(*) total, count(*) FILTER (WHERE is_enabled) enabled
FROM sources GROUP BY 1 ORDER BY 2 DESC;"
# доля постов с медиа по типам — потолок vision
docker compose exec -T postgres psql -U frontier -d frontier -c \
"SELECT s.source_type, count(*) posts, count(*) FILTER (WHERE p.has_media) with_media
FROM posts p JOIN sources s ON s.id = p.source_id GROUP BY 1 ORDER BY 2 DESC;"
# где на самом деле формируются стабильные тренды
docker compose exec -T postgres psql -U frontier -d frontier -c \
"SELECT workspace_id, count(*), max(detected_at) FROM trend_clusters GROUP BY 1 ORDER BY 2 DESC;"
# насколько неравномерно наполнены воркспейсы
docker compose exec -T postgres psql -U frontier -d frontier -c \
"SELECT workspace_id, count(*) FROM semantic_clusters GROUP BY 1 ORDER BY 2 DESC;"
# стадии индексации, включая vision
docker compose exec -T postgres psql -U frontier -d frontier -c \
"SELECT vision_status, count(*) FROM indexing_status GROUP BY 1 ORDER BY 2 DESC;"Источники правды, которые тут сознательно не продублированы: перечень воркспейсов —
config/workspaces.yml, перечень источников — таблица sources (в config/sources.yml
лежит её частичное зеркало), маршруты LLM — admin_runtime_settings.llm_routing_v1,
расписания заданий — дефолты полей admin_*_cron в shared/config.py.
Архитектура
┌─── SOURCES — контейнер ingest ───────────────────────────────────────────┐
│ │
│ telegram rss / atom web api │
│ Telethon feedparser httpx + bs4 JSON/CSV + cursor │
│ MTProxy RSS-пресеты селекторы в только Hacker News │
│ / SOCKS5 source.extra │
│ │
│ Часть источников ходит наружу через xray (sources.proxy_config). │
│ Расписание опроса — cron в config/sources.yml и в таблице sources. │
└──────────────────────────────────────────────────────────────────────────┘
│
▼
Redis Stream: stream:posts:parsed
{content, source_id, workspace_id, media_urls, linked_urls}
│
┌─── PROCESSING — контейнер worker, три консьюмера в одном процессе ───────┐
│ │
│ ┌── EnrichmentTask ← stream:posts:parsed ────────────────────────────┐ │
│ │ relevance → concepts → valence — три отдельных LLM-вызова: │ │
│ │ совместный relevance_concepts есть в коде, но выключен │ │
│ │ (GIGACHAT_RC_JOINT_ENABLED=false) │ │
│ │ → embed (EmbeddingsGigaR, 2560d) → Qdrant upsert │ │
│ │ → Neo4j upsert_concepts — инлайн, только запись │ │
│ │ → posts.tags — производное от concepts (weight >= 3), не вызов │ │
│ │ │ │
│ │ LLM: свой роутер поверх openai SDK. Основной провайдер │ │
│ │ wormsoft, фолбэки polza и GigaChat. Живая таблица маршрутов — │ │
│ │ admin_runtime_settings.llm_routing_v1, hot-reload. │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌── VisionTask ← stream:posts:vision ────────────────────────────────┐ │
│ │ S3 → GigaChat Vision (labels, scene, design signals) │ │
│ │ → PaddleOCR → post_enrichments(kind='vision'), posts.vision_labels │ │
│ │ → событие в stream:posts:reindex — в Qdrant пишет ReindexTask, │ │
│ │ сам VisionTask Qdrant не трогает │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌── ReindexTask ← stream:posts:reindex ──────────────────────────────┐ │
│ │ продюсеры события: vision_task (reason=vision), crawl4ai │ │
│ │ (reason=crawl), ops-скрипт enqueue_reindex_enriched_posts.py. │ │
│ │ Кнопка «reprocess» в админке шлёт пост не сюда, а заново │ │
│ │ в stream:posts:parsed │ │
│ └────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
│ │ │
│ │ ▼
│ │ stream:posts:crawl → контейнер crawl4ai
│ │ (внешние ссылки, найденные в постах)
│ ▼
│ stream:posts:enriched — консьюмерских групп нет
▼
┌─── ANALYTICS — APScheduler в контейнере admin, задания в субпроцессах ───┐
│ │
│ 35 3 * * * semantic clustering — связные компоненты графа │
│ косинусной близости (не HDBSCAN) │
│ 20 */8 * * * signal analysis: stable trend clusters + emerging │
│ signals + missing signals (SearXNG gap-анализ) │
│ 25 * * * * urgent trend alerts → Telegram │
│ раз в сутки retrospective, novelty judge, relevance audit, │
│ graph maintenance, entity resolution — контуры RSI │
│ │
│ Плюс служебные джобы: source scores (17 */6), обновление лимитов │
│ провайдеров, xray health. Полный список — admin/backend/scheduler.py. │
│ │
│ Расписания — дефолты shared/config.py, в .env не переопределены. │
└──────────────────────────────────────────────────────────────────────────┘
│
┌────┴──────────┬────────────────┬──────────────────┐
▼ ▼ ▼ ▼
Qdrant Neo4j PostgreSQL S3 / Cloud.ru
dense 2560d Concept 23 таблицы медиа-файлы
+ BM25 sparse Document posts, кластеры, + vision_summary
одна коллекция Workspace enrichments, (gzip)
на модель, MENTIONS indexing_status
алиасы RELATED_TO
*_active, CONTAINS
фильтр по ECHO_OF
workspace_id
│ │ │
└───────────────┴────────────────┴──────────────────┐
▼
┌─── MCP ──────────────────────────────────────────────────────────────────┐
│ │
│ mcp:8100 REST-слой, ~32 POST-роута (mcp/tools/*.py) │
│ mcp-gateway Streamable HTTP на /mcp, 22 инструмента — │
│ :8102 обёртки поверх REST. Аутентификации нет. │
│ │
│ Инструменты workspace-aware; синтез внутри части из них идёт │
│ маршрутом mcp_synthesis (сейчас GigaChat-2-Pro). │
└──────────────────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
Claude Code Claude Desktop
http://<host>:8102/mcp http://<host>:8102/mcp
Воркспейсов шесть: disruption, ai_trends, ai_research,
ai_products_media, design, auto_hmi — config/workspaces.ymlЧего на схеме нет и не должно быть:
Клиенты — Claude, а не Codex. Прежняя версия заканчивалась тремя «Codex Project» и тремя воркспейсами. Рабочих клиентов два — Claude Code и Claude Desktop, оба ходят в шлюз
:8102; воркспейсов шесть (config/workspaces.yml). Шлюз слушает LAN-адрес, поэтому из проектов на claude.ai он недоступен принципиально.
LangChain в стеке нет. Прежняя схема подписывала PROCESSING как «LangChain + GigaChat via gpt2giga-proxy», а цепочку — как
RunnableBranch. Ниlangchain, ниgigachainне ставит ни один Dockerfile стека и ни одинrequirements*.txt— в коде остались только упоминания словом (комментарий вworker/gigachat_client.py, устаревшие.cursor/rules/); обращения к моделям идут черезopenaiSDK и собственный роутер (shared/llm_routing.py,shared/llm_control_plane.py). GigaChat остался фолбэком и обслуживает эмбеддинги иmcp_synthesis, основная цепочка обогащения — на wormsoft.
HDBSCAN, community detection и album assembler не реализованы. Кластеризация — связные компоненты графа косинусной близости; в Neo4j нет ни Louvain/Leiden, ни иерархических summary, поэтому подпись «GraphRAG community detection» описывала замысел; альбомы схлопываются прямо в ingest, отдельного четырёхфазного сборщика нет.
«Reactive-пайплайн каждые 30 минут» не существует. Столбец
trend_clusters.pipelineпринимает единственное значениеstable;burst_score— поле внутри того же прогона, а не отдельный контур. Реальные каденции — на схеме.
Один хвост, который видно только по Redis: stream:posts:enriched пишется на каждом
успешном обогащении, но консьюмерских групп у него нет (XINFO GROUPS возвращает пусто).
Поток существует как точка расширения и как удобный след для отладки, потребителя у него
сейчас нет.
Технологический стек
Компонент | Образ / Библиотека | Роль |
LLM-роутинг |
| Маршрутизация по wormsoft / polza / openrouter / gigachat, таблица маршрутов в |
gpt2giga-proxy | локальная сборка, порт 8090 | GigaChat через OpenAI-совместимый интерфейс: эмбеддинги и |
Redis | redis:7-alpine | Event bus (Streams), кэш эмбеддингов TTL 7д |
PostgreSQL | postgres:16-alpine | 23 таблицы: посты, обогащения, кластеры, контур управления |
Qdrant | qdrant/qdrant:v1.17.0 | Hybrid search: dense 2560 Cosine + sparse BM25, доступ через алиасы |
Neo4j | neo4j:5.15-community | Граф ко-встречаемости концептов |
S3 / Cloud.ru | boto3, path-style | Медиафайлы, vision summaries gzip, ночные бэкапы |
crawl4ai | локальная сборка | Web-краулинг источников |
SearXNG | searxng/searxng | Self-hosted поиск для missing signals |
Prometheus + Grafana | standard | Метрики и дашборды, Alertmanager с двумя путями доставки |
xray | локальная сборка | Единственный egress наружу (socks5 |
Admin UI | FastAPI + Vue 3 (CDN, без сборки) | Управление: workspace, источники, темы, FinOps |
Чего в стеке нет, вопреки прежней редакции. Ни LangChain, ни gigachain не установлены ни в одном образе —
worker/gigachat_client.py:1прямо документирует отказ от них. Neo4j используется как граф ко-встречаемости: NER/NEL и community detection не реализованы (упоминанийgds., Louvain или Leiden в коде нет). Фронт админки — Vue 3 с vue-router, а не Vanilla JS.
Не используется из telegram-assistant: Supabase стек (kong/postgrest/studio/meta), Caddy, JWT/QR-auth, Mini App, мультитенантность (RLS), SaluteSpeech.
Telegram Bot API — исключение: он используется в проде для доставки алертов (
admin/backend/services/telegram_alerts.pyи прямой receiver Alertmanager). Запрет касается бота как пользовательского интерфейса, а не как транспорта уведомлений.
Admin UI — центр управления
Сервис admin — FastAPI-бэкенд плюс SPA на Vue 3 + vue-router. Библиотеки лежат
в репозитории (admin/frontend/vendor/vue.global.prod.js, vue-router.global.prod.js),
подключаются обычными <script>, сборки нет: экраны — нативные ES-модули с ленивым
() => import(). Порт 8101 опубликован на 0.0.0.0.
Слово «CDN» в таблице стека выше — неточность: из сети ничего не тянется.
admin/frontend/index.htmlподключает/static/vendor/vue.global.prod.jsи/static/vendor/vue-router.global.prod.js— оба файла лежат в репозитории и отдаются самим admin'ом.admin/frontend/js/app.jsстроитcreateRouter(createWebHashHistory()). Дорефакторинговый монолит сохранён какadmin/frontend-legacy.html— он лежит вне смонтированного каталогаadmin/frontendи наружу не отдаётся (GET /static/frontend-legacy.html→ 404, проверено 2026-08-04).
Роутинг — hash-history, адреса выглядят как http://<host>:8101/#/sources.
Catch-all @app.get("/{path:path}") (admin/backend/main.py) отдаёт index.html на всё,
что не перехвачено раньше него, — а раньше зарегистрированы ручки /api/*, монтирование
/static и обработка /metrics. Поэтому «глубокие» ссылки без # не работают:
GET /sources возвращает 200 и Dashboard, а не экран источников.
Где источник правды
Что | Файл | Команда сверки |
Маршруты SPA |
|
|
Пункты меню |
|
|
Подключённые API-роутеры |
|
|
Ручки конкретного роутера |
|
|
На 2026-08-04: 11 маршрутов фронта и 11 подключённых API-роутеров (каталог
admin/backend/routers/ — 11 модулей плюс __init__.py). Числа здесь не дублируются
списком намеренно: они меняются, а команды выше отвечают всегда.
Разделы
Один маршрут — один экран из admin/frontend/js/views/ (в каталоге есть ещё LoginView.js
и Placeholder.js — они маршрутов не имеют).
Маршрут | Экран | Основной API |
| Dashboard: сводка по workspace |
|
| Статус scheduler'а, Redis-стримы, ручной запуск джобов, reprocess поста |
|
| Список источников, добавление, toggle, vision, telegram-handle, bootstrap |
|
| Посты и карточка поста |
|
| Медиа-группы Telegram |
|
| Медиафайлы по sha256 |
|
| Семантические кластеры, тренды, emerging и missing signals, timeline |
|
| Граф концептов (Neo4j, рендер через |
|
|
|
|
| Создание и правка workspace, toggle, bootstrap |
|
| Четыре вкладки: Runtime и роутинг · Провайдеры и FinOps · Xray · Система |
|
Маршрута
/signalsнет — ни в текущем фронте, ни в сохранённомfrontend-legacy.html. В прежней версии раздела на него вела ссылка из таблицы «Что управляется через UI (не через конфиги)». Catch-all{ path: '/:pathMatch(.*)*', redirect: '/' }молча уводит такой адрес на Dashboard — отсюда и ощущение, что «раздел был, но пропал».
DLQ-экрана в UI нет. Прежний текст обещал на
/pipelineраздел «DLQ: проблемные документы». НиPipelineView.js, ниrouters/pipeline.pyсловаdlqне содержат. Живых вхождений во всей админке два, и оба — не экран:routers/settings.pyотдаётvision_dlq_streamкак read-only значение конфига на вкладку «Система», аfrontend-legacy.htmlупоминает DLQ в подсказках к тем же полям.VISION_DLQ_STREAM— это Redis-стрим, читать его нужноredis-cli.
Ручной запуск джобов живёт на /pipeline; набор кнопок — const JOBS в
admin/frontend/js/views/PipelineView.js (на 2026-08-04 их четыре). Три из них —
Signal analysis, Missing signals, Semantic clusters — уходят через launch_manual_job
в субпроцесс python -m admin.backend.manual_jobs, чтобы CPU-bound работа не блокировала
event loop admin. Четвёртая, Refresh source scores, субпроцесса не создаёт:
POST /api/pipeline/refresh-source-scores вызывает refresh_source_scores_job прямо
в процессе admin.
Авторизация
HTTP-middleware _auth в admin/backend/main.py и его предикат _path_needs_auth:
закрыто всё под /api/, кроме /api/health, /api/auth/login и
/api/monitoring/alertmanager/webhook; отдельно пропускается любой запрос методом
OPTIONS. /metrics открыт. Статика и сам SPA публичны — форма входа рисуется
до авторизации.
Два способа: cookie-сессия fadmin_session (HMAC на ADMIN_PASSWORD, TTL 7 дней) — для
браузера, и HTTP Basic — для curl и интеграций. 401 отдаётся JSON'ом без
WWW-Authenticate, чтобы браузер не подвешивал XHR нативным диалогом. Поэтому голый
curl http://127.0.0.1:8101/api/sources → 401 {"detail":"unauthorized"} — это норма.
Что действительно управляется через UI
Настройка | Где | Пишет ли UI |
Workspace: имя, категории, design lenses, bridges, |
| да ( |
Workspace: |
| да, сливается в |
Workspace: активность |
| да ( |
Источник: создание и удаление |
| да |
Источник: вкл/выкл |
| да ( |
Источник: vision, telegram-handle |
| да |
Источник: url, cron, |
| только при создании, отдельного |
Runtime-mode и политика роутинга v2 |
| да, пишется в |
Legacy |
| нет, только чтение — см. предупреждение ниже |
Xray: ремедиация, failover, rollback |
| да |
Провайдеры, бюджеты, circuits, FinOps |
| нет, только чтение |
Пороги indexing/vision/relevance, интеграции, секреты |
| нет, только чтение |
Пороги missing signals ( | — | нет; переменные окружения в |
Темы для gap-анализа | — | нет; темы предлагает LLM, фолбэк — |
Три ключа
admin_runtime_settings— не три редактируемых экрана. Живых ключей в таблице ровно три:runtime_mode,llm_control_plane_policy_v2,llm_routing_v1. Первые два UI пишет (POST /api/settings/runtime-mode,POST /api/settings/policy), аllm_routing_v1показывает только на чтение — вRuntimeRoutingTab.jsэтот блок так и подписан: «Унаследованный per-task роутинг — только для справки». РучкаPOST /api/settings/llm-routingв API есть, контрола под неё в UI нет; менять per-task маршруты приходится запросом мимо интерфейса.
API workspaces: PATCH /api/workspaces/{id} — частичное обновление (Pydantic
exclude_unset); relevance_threshold сливается в JSON relevance_weights через
merge_relevance_weights, категорийные веса (technology, design, …) не затираются.
POST /api/workspaces при upsert делает то же слияние, но в его ON CONFLICT DO UPDATE
нет is_active — активность через этот вызов не меняется. Меняют её PATCH /toggle,
bootstrap из YAML и — если положить поле в тело — тот же PATCH /api/workspaces/{id}
(is_active объявлен в WorkspaceUpdate); из UI это поле не отправляется.
Приёмка после смены порога или категорий: логи worker на relevance_category_unknown;
GET /api/pipeline/stats. Кэш workspace в worker живёт до 90 с
(worker/tasks/enrichment_task.py, _get_workspace) — раньше этого срока изменение
не проявится.
Кластеры vs category: семантические trend_clusters и поле posts.category — разные оси;
кластер не подменяется ненадёжной классификацией поста. Обоснование — в докстринге
shared/models/trend_cluster.py (сам модуль описателен и в рантайме не используется).
Типы источников в форме добавления: telegram, rss, web, api, email
(shared/source_definitions.CANONICAL_SOURCE_TYPES). habr — не тип, а устаревший алиас
rss плюс набор пресетов RSS_PRESETS.
config/*.yml и БД: две асимметрии, обе кусаются
Утверждение «
config/содержит только дефолты при первом запуске, дальше источник правды — БД» неверно.admin/backend/services/bootstrap_configs.pyделаетINSERT ... ON CONFLICT (id) DO UPDATE SET ... is_enabled = EXCLUDED.is_enabled(и то же дляschedule_cron,proxy_config,extra,source_authority, а у воркспейсов — дляis_active). Любой вызовPOST /api/sources/bootstrapперезаписывает БД из YAML.
Из этого следуют два несимметричных перекоса.
1. YAML сильнее БД там, где пересекается. Источник, выключенный руками через
PATCH /api/sources/{id}/toggle, будет молча включён обратно ближайшим bootstrap'ом, если в
config/sources.yml у него is_enabled: true (или ключ вовсе отсутствует — bootstrap_configs.py
читает его как bool(src.get("is_enabled", True)), то есть «нет ключа» = включён).
Ответ ручки при этом успешный. Включение или выключение источника — два действия:
PATCH /toggle и правка config/sources.yml. Регрессию ловит
tests/test_sources_config_contract.py, но узко: test_auto_batch_enabled_flags_match_the_recorded_state
сверяет флаги только у батча auto_* (десять источников). Остальные записи sources.yml
не подстрахованы ничем — за ними следит только дисциплина.
2. БД шире YAML там, где не пересекается. config/sources.yml — частичное зеркало,
а не полный список: bootstrap только вставляет и обновляет, ничего не удаляет.
Замер 2026-08-04: в БД 247 источников (202 включённых), в sources.yml — 168 (133 включённых),
79 записей существуют только в БД, ни одной — только в YAML. Расхождений по is_enabled
в этот день нет, но это следствие дисциплины, а не механизма. Пересчитать:
docker exec frontier-intelligence-postgres-1 sh -lc \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -c \
"SELECT count(*), count(*) FILTER (WHERE is_enabled) FROM sources"' </dev/null
docker exec frontier-intelligence-admin-1 python -c \
"import yaml; d=yaml.safe_load(open('/app/config/sources.yml'))['sources']; \
print(len(d), sum(1 for s in d if s.get('is_enabled', True)))" </dev/null</dev/null обязателен: docker exec без него вычитывает остаток скрипта из stdin.
config/ запечён в образ
У контейнера admin смонтированы ровно два bind-mount'а: /runtime и
/app/admin/frontend (ro). Каталог config/ в списке отсутствует — он попадает внутрь
строкой COPY config/ /app/config/ в admin/Dockerfile.
docker inspect frontier-intelligence-admin-1 \
--format '{{range .Mounts}}{{.Destination}} {{end}}'
# → /runtime /app/admin/frontendПрактические следствия:
Правка
config/*.ymlлокально плюс rsync не меняет ничего, пока файл не окажется внутри контейнера. Варианты:docker cp config/sources.yml <admin>:/app/config/(быстро, живёт до пересоздания контейнера) либо пересборка образа —bash scripts/server-build-stack.sh admin.COPY config/ /app/config/есть ровно в двух Dockerfile'ах —admin/иmcp/. Читают эти файлы тоже только двое:admin/backend/services/bootstrap_configs.py(CONFIG_DIR = Path("/app/config")) иmcp/guards.py(/app/config/workspaces.yml).workerиingestYAML-конфиги не читают вообще — они работают от таблицsources/workspaces. Сверить:grep -l "COPY config" */Dockerfile.docker cpв admin не доезжает до mcp — это два независимых экземпляра файла. Замер 2026-08-04:workspaces.ymlна хосте 11789 байт, вadmin— те же 11789, вmcp— 9701 (копия суточной давности).Правки фронта, наоборот, применяются мгновенно:
admin/frontendсмонтирован с хоста, а FastAPI отдаёт статику с диска и ставитCache-Control: no-cache, must-revalidate.Правки Python в
admin/backend/требуют пересборки образа — код запечён,restartего не обновит.
Каталог
/app/configвнутриworkerиingestвсё-таки есть — и он мёртвый. В их Dockerfile'ахCOPY config/нет; файлы достались от более ранних сборок и с тех пор не обновлялись:sources.ymlтам 24 282 байта от 3 мая против 80 334 от 3 августа уadminиmcp. Ни одна строка вworker/иingest/эти файлы не открывает. Правка там не влияет ни на что — сверять и чинить конфиг нужно вadminиmcp.
База данных
Схема здесь намеренно не дублируется. Источник правды —
storage/postgres/init.sqlиstorage/postgres/migrations/*.sql; живая база сверяется так:docker exec -it frontier-intelligence-postgres-1 \ sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "\d posts"'До 04.08.2026 в этом разделе лежали рукописные
CREATE TABLE, разошедшиеся с базой по именам колонок, типам и самому составу таблиц: фигурировали несуществующиеpost_media_mapиmedia_group_items,sources.type/enabled/scheduleвместо реальныхsource_type/is_enabled/schedule_cron,UUID PRIMARY KEYвместоTEXT. По такому описанию нельзя было написать ни одного рабочего запроса. Поэтому теперь здесь инвентарь назначений, а не копия DDL — копия неизбежно расходится снова.
PostgreSQL — 23 таблицы
Первичные ключи везде TEXT, не UUID. workspace_id TEXT NOT NULL — в каждой
основной таблице; дочерние (indexing_status, post_enrichments, source_checkpoints,
source_runs) его не несут и скоупятся через родителя.
Приём и обогащение
Таблица | Назначение | Значимые ограничения |
| Пространства. Пер-воркспейсные настройки живут в | — |
| Источники. Расписание в |
|
| Курсоры инкрементального опроса: | — |
| Журнал прогонов источников |
|
| Документы. Категория — | — |
| Результаты обогащения, тяжёлое тело — в S3 по |
|
| Статусы конвейера по посту |
|
| Медиа по содержимому. Первичный ключ — сам | — |
| Телеграм-альбомы: | — |
| Решения о релевантности с обоснованием. | — |
Аналитический слой
Таблица | Назначение | Значимые ограничения |
| Семантические кластеры окна | — |
| Стабильные тренды. |
|
| Ранние сигналы |
|
| Оконные ряды по сущностям |
|
| Снимки слабых сигналов + вердикт судьи ( | — |
| Журнал прогонов кластеризации: пороги, сводка, метрики качества |
|
| Пробелы, найденные через SearXNG | — |
| Отправленные срочные алерты, дедуп по |
|
Управление и обратная связь
Таблица | Назначение | Значимые ограничения |
| Предложения изменить пороги (контур RSI), человеческий гейт |
|
| Предложения слить сущности графа | то же |
| Редакторская разметка карточек |
|
| Ручные запуски задач из админки | — |
| Живые настройки с hot-reload, в том числе таблица маршрутизации LLM | Не создаётся ни |
Слой провенанса. Миграция 20260714_provenance_dedup.sql добавила по шесть колонок
в semantic_clusters, trend_clusters и emerging_signals: deduped_source_count,
distinct_voices, echo_ratio, arrival_dispersion, distinct_originators,
independence_score. Значение по умолчанию 0.0, исторические строки не бэкфилились —
0.0 означает «не измерено», а не «полностью синдицировано». Считает их
shared/provenance.py, который импортируется ровно одним модулем
(worker/services/semantic_clustering.py) и до поисковых поверхностей не доходит.
Учёта миграций нет. Ни Alembic, ни таблицы schema_migrations: применённость
проверяется наличием объектов, а не журналом. Список миграций — storage/postgres/migrations/.
Qdrant — коллекции
Одна коллекция на модель эмбеддингов, а не по коллекции на воркспейс. Изоляция —
payload-фильтром по workspace_id в каждом запросе (worker/integrations/qdrant_client.py).
Имя строится как {база}__{версия}__{семейство индекса} в shared/embedding_models.py.
Обращаться нужно через алиасы — прямое имя коллекции в коде считается ошибкой:
Алиас | Указывает на | Векторы |
|
| dense 2560 Cosine + sparse (BM25) |
|
| dense 2560 Cosine, sparse нет |
Осторожно: базовые коллекции живы, но мертвы.
frontier_docsиtrend_clustersостались после перехода на версионированные имена, не являются целями ни одного алиаса и не обновляются с момента cutover. Прямой запрос по такому имени тихо вернёт срез полугодовой давности. Скрипты перехода —scripts/qdrant_alias_cutover.pyиscripts/qdrant_backfill_versioned.py.
Коллекция личного корпуса (own_corpus, вторая ось own_stake) не создана:
код готов, флаг OWN_STAKE_ENABLED=false, scripts/index_own_corpus.py в проде
не запускался. С 04.08.2026 init_storage.py её больше и не заводит.
Neo4j — граф концептов
Это граф ко-встречаемости концептов, а не провенанса. Что в нём реально есть:
Метка | Что | Порядок |
| Извлечённые концепты | сотни тысяч |
| Документы | ~190 тыс. |
| Корни пространств | 7 (шесть рабочих плюс мусорный узел |
Связь | Что означает |
| документ упоминает концепт |
| ко-встречаемость концептов, со счётчиком |
| воркспейс содержит концепт |
| слой провенанса: перепечатка/эхо |
Чего в графе нет, вопреки прежнему описанию. Метки
SourceиTrendClusterсуществуют только потому, что их породили констрейнты изstorage/neo4j/constraints.cypher— узлов ноль. МеткиAlbumнет даже там. СвязейFROM_SOURCE,EVOLVED_FROM,BRIDGESне существует:CALL db.relationshipTypes()возвращает ровно четыре типа из таблицы выше. Community detection, Louvain/Leiden и иерархические summary из GraphRAG не реализованы — упоминанийgds.в коде нет.
Из графа ничего не удаляется. У Neo4jFrontierClient нет ни одного метода удаления:
когда пост признан нерелевантным и вычищен из Qdrant, его (:Document) с рёбрами
остаётся навсегда, а счётчики RELATED_TO продолжают его учитывать.
MCP Tools
Инструменты живут в двух контурах, и это не одно и то же множество:
Контур | Что это | Адрес | Сколько инструментов |
REST | FastAPI-приложение |
| 32 |
MCP-шлюз |
|
| 22 |
Шлюз — строгое подмножество REST: все 22 его имени есть среди 32 REST-имён, обратного
включения нет. MCP-клиенты (Claude Code, Claude Desktop) подключены к :8102 — то есть
видят 22 из 32. Сколько всего клиентов реально подключено к шлюзу, изнутри стека
не проверяется: аутентификации нет и учёта подключений он не ведёт.
Источник правды и как сверить
Списки инструментов меняются чаще, чем этот файл. Не верь таблицам — спрашивай стек:
# 32 из REST. Ответ — объект {"tools": [...]}, длину брать у списка внутри:
# len() от самого объекта вернёт 1 (число ключей), а не число инструментов.
curl -sS http://127.0.0.1:8100/tools | python3 -c \
"import sys,json;print(len(json.load(sys.stdin)['tools']))"
# 22 из шлюза — счётчик по коду; отдельного эндпоинта со списком у него нет
grep -c '@mcp.tool' /opt/frontier-intelligence/mcp/mcp_gateway.pyРегистрация REST-инструментов — app.include_router(...) в mcp/server.py:
Модуль | Семейство | Инструментов | Есть в шлюзе |
| поиск, граф, brief, ingest_url | 7 (по одному на модуль) | да |
| workspaces, sources, clusters, signals, timeline | 12 | да |
| редакционная петля карточек | 3 | да |
| здоровье графа, слияние сущностей | 4 | нет |
| пороги, недооценённые сигналы, аудит релевантности | 6 | нет |
Разрыв REST ↔ шлюз: 10 инструментов клиенту недоступны
В шлюз не выведены два целых модуля.
mcp/mcp_gateway.pyсодержит ровно 22@mcp.tool, и ни одного из перечисленных ниже среди них нет. По REST (curl .../tools) они отвечают, в MCP-клиенте их не существует — это не сбой подключения и не кэш клиента.
Из graph_health.py: get_graph_health, list_entity_merge_proposals,
approve_entity_merge, reject_entity_merge.
Из threshold_proposals.py: list_threshold_proposals, list_underrated_signals,
list_relevance_audit_sample, mark_relevance_audit, approve_threshold_change,
reject_threshold_change.
Практическое следствие: RSI-петля (одобрение слияний сущностей и изменений порогов)
из Claude Code не управляется — только прямым curl в :8100 с сервера.
Прежняя редакция этого раздела перечисляла 19 инструментов одной таблицей. Недоставало 13: три редакционных (
export_inbox_cards,record_card_feedback,list_card_feedback— они в шлюзе есть) и десять выше.
Транспорт
Шлюз работает по Streamable HTTP, а не по SSE: mcp/mcp_gateway.py заканчивается
mcp.run(transport="streamable-http"), docstring говорит то же. Строки transport="sse"
в файле нет.
Формулировка «SSE gateway» неверна и была источником ложных диагнозов при отладке подключения. Голый
GET http://127.0.0.1:8102/mcpотдаёт406, а не поток событий — это нормальный ответ Streamable HTTP на запрос без нужногоAccept, а не поломка.
Доступ и аутентификация
mcp:8100опубликован только на loopback (127.0.0.1:8100->8100/tcpвdocker compose ps).mcp-gateway:8102опубликован на0.0.0.0и не имеет аутентификации вообще: вmcp/server.pyнет ни одной auth-зависимости, аTransportSecuritySettingsшлюза заданы какallowed_hosts=["*"],allowed_origins=["*"],enable_dns_rebinding_protection=False.
Это осознанное решение владельца: хост живёт в локальной сети, наружу не проброшен.
Учитывать при этом надо, что через :8102 доступны пишущие инструменты —
ingest_url (кладёт задание в stream:posts:crawl) и record_card_feedback
(пишет в card_feedback). Остальные пишущие (approve_*/reject_*, mark_relevance_audit)
остались за REST-контуром и потому недостижимы снаружи. См.
docs/TODO-UNFINISHED.md.
Общие правила инструментов
workspaceпринимают 17 из 22 инструментов шлюза. Пять не принимают, и это не упущение: они адресуются идентификатором сущности (get_cluster_details,get_cluster_evidence,get_missing_signal_details,get_source_details) либо не привязаны к пространству (list_workspaces).В Qdrant ходят три поисковых инструмента из четырёх:
search_frontier,search_balanced(переиспользуетrun_search_requestизsearch_frontier) иsearch_trend_clusters— hybrid dense + sparse с payload-фильтром поworkspace_id.search_by_vision— исключение: это сырой SQL поpost_enrichmentsв PostgreSQL, Qdrant там не участвует.Hybrid — свойство образа, а не гарантия:
mcp/server.pyстроит описаниеsearch_frontierчерезHAS_SPARSEи при отсутствии fastembed честно объявляет «dense only». В работающем стеке 04.08.2026curl .../toolsотдаёт вариант с «dense embeddings + BM25 sparse».Имена коллекций конфигурируемы (
QDRANT_COLLECTION,QDRANT_TRENDS_COLLECTIONи их*_ALIAS), захардкоженных имён в инструментах нет: поиск по строковым литераламgrep -rE "['\"](frontier_docs|trend_clusters)['\"]" mcp/ --include=*.pyдаёт ноль (без кавычек в шаблоне — 19 попаданий, все в текстах описаний). Значения —shared/config.py.synthesize=trueподдерживают ровно три инструмента:search_frontier,search_balanced,get_frontier_brief.
Синтез не привязан к GigaChat намертво. Модель берётся из маршрута
mcp_synthesisвadmin_runtime_settings.llm_routing_v1с hot-reload. На 04.08.2026 там действительноgigachat / GigaChat-2-Pro, но это состояние таблицы, а не свойство кода: маршрут меняется без пересборки. Сверка:docker compose exec -T postgres psql -U frontier -d frontier -Atc \ "select value from admin_runtime_settings where key='llm_routing_v1'" </dev/null \ | python3 -m json.tool | grep -A4 mcp_synthesis
Admin UI ходит в REST-контур узко:
admin/backend/routers/search.pyдёргаетsearch_frontierиsearch_balancedчерезsettings.mcp_internal_url(http://mcp:8100). Остальные 30 инструментов админка не вызывает.
Прежняя формулировка «Admin UI тоже ходит в эти tools» преувеличивала.
grep -rl --include=*.py mcp_internal_url admin/даёт два файла, и во втором (routers/settings.py) URL только выводится в дампе конфигурации, вызовов нет. Фильтр по*.pyобязателен: без него в выдачу попадают ещё четыре__pycache__/*.pyc.
Известная ловушка параметра
search_trend_clusters объявляет pipeline: Literal["stable", "reactive"] | None = "stable",
и значение "reactive" проходит валидацию без ошибки — но в данных его не существует:
docker compose exec -T postgres psql -U frontier -d frontier -Atc \
"select pipeline, count(*) from trend_clusters group by 1" </dev/null
# 04.08.2026: единственная строка stable|393Вызов с pipeline="reactive" возвращает {"results": []} и эхо фильтра в
applied_filters — пустой результат, а не ошибку.
Reactive-ветки детектора нет в коде — это не «временно не запускается». Единственное место, которое пишет в
trend_clusters, —worker/services/semantic_clustering.py, и оно проставляет"pipeline": "stable"константой. В коде и схеме словоreactiveвстречается только декларативно — объявление типа выше, текст описания вmcp/server.py,CHECK (pipeline IN ('reactive', 'stable'))вstorage/postgres/init.sqlи комментарий вshared/models/trend_cluster.py. Ни одной ветки, которая бы его писала, нет; сверка:grep -rE "reactive" --include=*.py --include=*.sql --include=*.yml .(в
docs/упоминаний больше — но это тексты, не код). Раздел Trend Detection ниже по этому же файлу описывает «Reactive (cron 30 мин)» как работающий контур — это описание замысла, кода за ним нет.
Processing Pipeline
As-built: событийная цепочка
Всё обогащение — один процесс worker (worker/main.py), который держит три консьюмера
в одном event-loop: EnrichmentTask, VisionTask, ReindexTask. Четвёртый консьюмер живёт
в отдельном сервисе crawl4ai.
ingest (telegram / rss / web / api / email)
медиа → S3, s3_keys кладутся в media_urls события
→ stream:posts:parsed
│
└→ EnrichmentTask
├ post в PostgreSQL, indexing_status=pending
├ has_media → stream:posts:vision (публикует ИМЕННО enrichment, не ingest)
├ relevance → нерелевантный: indexing_status=dropped, точка в Qdrant удаляется
└ релевантный: concepts → valence → embedding текста поста
├ Qdrant upsert, Neo4j concepts
├ post_enrichments (concepts, tags, valence)
├ linked_urls → stream:posts:crawl
└ stream:posts:enriched ← пишется, но НИКЕМ не читается
VisionTask: stream:posts:vision → S3 → vision-модель (+ PaddleOCR)
→ post_enrichments(kind='vision') → stream:posts:reindex
crawl4ai: stream:posts:crawl → crawl
→ post_enrichments(kind='crawl') → stream:posts:reindex
ReindexTask: stream:posts:reindex → enriched-текст → Qdrant patch + Neo4j conceptsurlв событии — permalink поста (валидация источника).linked_urls— внешние HTTP(S)-ссылки из текста и Telethon-entities;t.me,twitter.com,x.comотбрасываются, потолокMAX_LINKED_URLS = 10(shared/linked_urls.py). Только они уходят в crawl4ai.Альбомы сворачиваются на стороне ingest в один
PostParsedEventсgrouped_idи несколькимиmedia_urls(ingest/sources/telegram_source.py).Классов
AlbumAssemblerTaskиGraphTaskв коде нет вовсе — не «не подключены», а отсутствуют (grep -ril "AlbumAssemblerTask\|GraphTask" . --include=*.py→ пусто). Вworker/tasks/лежат ровно три файла (enrichment_task.py,vision_task.py,reindex_task.py), граф пишется inline вEnrichmentTaskи патчитсяReindexTask.
Vision-событие публикует
EnrichmentTask, а не ingest. Прежняя редакция (и раздел «As-Built Runtime» ниже по документу) утверждала «ingest uploads media to S3 and emitsstream:posts:vision». Ingest грузит медиа в S3 и кладёт s3-ключи вmedia_urls, аxaddвstream:posts:visionделает толькоworker/tasks/enrichment_task.py(константаSTREAM_VISION, публикация внутриprocess_event). Вingest/имени этого стрима нет ни разу — проверяется одним grep:grep -r "stream:posts:vision" ingest/ --include=*.py # пусто grep -rl "STREAM_VISION" worker/ --include=*.py # только tasks/enrichment_task.py
Redis Streams
Канонический список стрим/группа — shared/redis_streams.py::DEFAULT_STREAM_GROUPS;
именно он используется и мониторингом (GET /api/pipeline/streams).
Stream | Пишет | Consumer group | Читает |
| ingest, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| нет ни одной | никто |
stream:posts:enriched— не «observer stream для downstream-интеграций», а тупик. Проверено 04.08.2026 на живом стеке:XINFO GROUPS stream:posts:enrichedвозвращает пустой список — consumer-групп ноль, аXLENпри этом стоит у потолкаSTREAM_MAXLEN(в замере 10013 при потолке 10 000: тримминг приблизительный, длина слегка гуляет). То есть события пишутся и вытесняются, ни разу никем не прочитанные. Любой код, рассчитывающий «подписаться и получить историю», получит только последние ~10k записей и ничего из прошлого.docker exec frontier-intelligence-redis-1 redis-cli XINFO GROUPS stream:posts:enriched
Потолок длины у всех стримов — STREAM_MAXLEN = 10_000 (shared/redis_client.py, там же
разбор OOM 31.07.2026). DLQ-стримы stream:posts:parsed:dlq и stream:posts:vision:dlq
объявлены в shared/config.py, но на 04.08.2026 обоих ключей в Redis нет — ни одно сообщение
туда ещё не уезжало.
Проверка состояния (docker exec — контейнер называется frontier-intelligence-redis-1,
не redis; профили COMPOSE не нужны, если ходить по имени контейнера):
docker exec frontier-intelligence-redis-1 redis-cli --scan --pattern 'stream:*'
docker exec frontier-intelligence-redis-1 redis-cli XLEN stream:posts:parsed
docker exec frontier-intelligence-redis-1 redis-cli XINFO GROUPS stream:posts:parsedEnrichmentTask
Порядок: валидация источника → _save_post → (при медиа) публикация vision-события →
relevance → при прохождении порога concepts → valence → эмбеддинг первых 2000
символов текста поста → Qdrant upsert → Neo4j → post_enrichments (concepts, tags,
valence) → indexing_status=done.
Модели не зашиты в код: все четыре задачи (
relevance,concepts,valence,relevance_concepts) идут черезLLMRouterClientи живую таблицуadmin_runtime_settings.llm_routing_v1. Смотреть провайдера по факту, а не по документу: в логах worker строкаllm_task task=... provider=... actual_model=....Совмещённая цепочка
relevance_concepts(один вызов вместо двух) существует, но включается флагомGIGACHAT_RC_JOINT_ENABLED; на 04.08.2026 онfalse, работают две отдельные цепочки.relevant=Falseиз-за сбоя LLM не считается вердиктом:status=="failed"поднимает исключение и уводит событие в общий retry, чтобы провал провайдера не выглядел как честный «нерелевантно».Нерелевантные посты помечаются
dropped, существующая точка в Qdrant удаляется.Параллелизм батча —
INDEXING_MAX_CONCURRENCY(на сервере4).Poison-сообщения (превышен
delivery_count) уезжают вINDEXING_DLQ_STREAM.
Релевантность считает не GigaChat. По живой таблице маршрутов на 04.08.2026 у всех четырёх задач основной маршрут —
wormsoft/agent/medium. Цепочки фолбэков разные: уrelevanceиrelevance_conceptsэтоpolza(google/gemma-4-31b-it) →gigachat(GigaChat-2), уconceptsиvalence— сразуgigachat(GigaChat-2), без polza. За GigaChat остались эмбеддинги (EmbeddingsGigaR) иmcp_synthesis(GigaChat-2-Pro). Прежняя формулировка «Релевантность (GigaChat)» описывает контур до июльской перекладки маршрутов. Смотреть живую таблицу целиком:docker exec frontier-intelligence-postgres-1 psql -U frontier -d frontier \ -tAc "SELECT value FROM admin_runtime_settings WHERE key='llm_routing_v1';"
VisionTask
stream:posts:vision → скачивание из S3 → vision-модель → при заданном PADDLEOCR_URL
дополнительно PaddleOCR POST /v1/ocr/upload → в enrichment попадают paddle_ocr_text
и объединённый ocr_text → post_enrichments(kind='vision'), indexing_status.vision_status,
для альбомов — gzip-сводка в S3 и media_groups. При vision_status='done' публикуется
stream:posts:reindex.
Политика на источник — sources.extra.vision: mode ∈ {full, ocr_only, skip},
max_media_bytes (дефолт 9 МБ). PaddleOCR запускается при mode != skip и mime image/*.
Сервис: docker compose --profile paddleocr up -d paddleocr, в окружении worker
PADDLEOCR_URL=http://paddleocr:8008.
Провайдер vision выбирается роутером, и «GigaChat Vision» сейчас не вызывается вообще. Порядок кандидатов берёт
worker/llm_router_client.py::_generate_visionиз семействаvision_generationживой политикиadmin_runtime_settings.llm_control_plane_policy_v2. Если среди кандидатов есть wormsoft — он идёт первым для всех источников, дальше openrouter → polza → gigachat, а ветка поquality_tier(_vision_provider_for_quality_tier:trusted/primary→ GigaChat, иначе openrouter приVISION_ROUTING_ENABLED) не выполняется вовсе — она достижима только когда wormsoft из политики убран. На 04.08.2026wormsoft/vision/mediumв политике включён, и по последним 300 vision-обогащениям вызовов GigaChat ноль:trusted,primaryиstandardодинаково уходят в wormsoft, с фолбэком на polza (qwen/qwen3-vl-30b-a3b-instruct). Записи «GigaChat Vision» в старой редакции не верны ни для одногоquality_tier. Кто на самом деле обрабатывал картинки — видно в самих обогащениях:docker exec frontier-intelligence-postgres-1 psql -U frontier -d frontier -tAc \ "SELECT it->>'quality_tier' AS tier, it->'_llm'->>'provider' AS provider, count(*) FROM (SELECT data FROM post_enrichments WHERE kind='vision' ORDER BY created_at DESC LIMIT 300) t, jsonb_array_elements(t.data->'items') it GROUP BY 1,2 ORDER BY 3 DESC;"
Расписания (планировщик admin)
Задачи регистрируются в admin/backend/scheduler.py::_build_scheduler, дефолтные краны —
поля admin_*_cron в shared/config.py, переопределяются через .env/environment в compose.
Полный список здесь намеренно не дублируется — он растёт (на 04.08.2026 шестнадцать задач)
и устаревает быстрее документа.
# какие задачи вообще есть — из кода
grep -E 'id="[a-z_]+"' admin/backend/scheduler.py
# живое состояние с next_run_time (нужна авторизация admin)
curl -sS -u admin:"$ADMIN_PASSWORD" http://127.0.0.1:8101/api/pipeline/scheduler
docker exec … env | grep CRONдаёт неполную картину. В окружении контейнера admin на 04.08.2026 лежат только шесть переменных*_CRON(openrouter × 4, trend alert, xray); остальные десять задач работают на дефолтах изshared/config.pyи вenvне видны вообще. Отсутствие переменной означает «дефолт», а не «задачи нет».
Три опорных расписания, на которые ссылаются остальные разделы:
Задача | Переменная | Дефолт | Что делает |
|
|
| семантические кластеры + сигналы, раз в сутки |
|
|
| сигналы поверх готовых кластеров + missing signals, трижды в сутки |
|
|
| пересчёт |
Ручной запуск есть не у всех задач. admin/backend/manual_jobs.py — отдельный процесс,
чтобы CPU-bound работа не блокировала event-loop admin; список поддерживаемых имён — функция
_dispatch в этом же файле (на 04.08.2026 их десять из шестнадцати; refresh_openrouter_*,
refresh_gigachat_balance, xray_health_check, urgent_trend_alerts через него не гоняются).
Наружу по HTTP выведено ещё меньше — пять ручек:
grep -E '@router.post\("/(run|refresh)-' admin/backend/routers/pipeline.py
run_missing_signalsне имеет расписания вообще. В_build_schedulerнет ни одногоadd_jobс этим id и нет переменнойADMIN_MISSING_SIGNALS_CRON— прежняя строка «Missing Signals (cron сутки)» описывает несуществующую задачу. Отдельно gap-анализ запускается только руками:POST /api/pipeline/run-missing-signalsилиpython -m admin.backend.manual_jobs run_missing_signals <workspace|__all__>. При этом самиmissing_signalsобновляются — внутриrun_signal_analysis(worker/services/semantic_clustering.py→run_missing_signals_analysis), то есть по крану20 */8 * * *. Сверять так:max(updated_at)вmissing_signalsдолжен попадать в окно последней серииcluster_runs.stage='signal-analysis'. Точного совпадения с самым последним запуском ждать не надо — задача идёт по воркспейсам по очереди, а при пустом наборе тем строки воркспейса намеренно не переписываются (_replace_missing_signals→ логmissing_signals_replace_skipped). Замер 04.08.2026:max(updated_at) = 08:25:37при трёх запусках в 08:24:52 / 08:25:37 / 08:26:36.docker exec frontier-intelligence-postgres-1 psql -U frontier -d frontier -tAc \ "SELECT (SELECT max(updated_at) FROM missing_signals) AS ms_updated, (SELECT max(started_at) FROM cluster_runs WHERE stage='signal-analysis') AS last_run;"
Trend Detection
Пайплайн один. Стадии две, и они разнесены по разным задачам планировщика.
Стадия 1 — семантические кластеры (run_semantic_clusters, раз в сутки)
посты за окно → векторы из Qdrant
→ связные компоненты по косинусной близости с ограничением разрыва по времени
→ semantic_clusters + signal_time_series
Стадия 2 — сигналы (внутри обеих задач; отдельно — run_signal_analysis, 3×/сут)
группировка семантических кластеров → на группу считаются
burst, velocity, acceleration, coherence, novelty,
source_diversity, freshness, evidence_strength, change_point
→ взвешенная сумма signal_score (веса и пороги — в настройках кластеризации)
→ stage:
stable → trend_clusters (+ LLM-бриф title_ru / insight / opportunity)
emerging → emerging_signals
weak → weak_signal_snapshots (материал для ретро-петли RSI)
fading → пометка жизненного цикла
→ зеркалирование stable-кластеров в Qdrant для search_trend_clusters
Стадия 3 — missing signals (только внутри run_signal_analysis)
генерация тем по воркспейсу → SearXNG → сравнение с внутренним корпусом
→ gap_score → missing_signalsКластеризация — связные компоненты по косинусу, а не HDBSCAN: пакета
hdbscanнет ни в одном requirements и ни в одном импорте. В Python-коде слово встречается ровно один раз — как строка-фикстура вtests/test_neo4j_concepts.py, к алгоритму отношения не имеет (grep -ril hdbscan . --include=*.py). В документации оно ещё живо — в том числе выше в этом же файле, в ASCII-схеме архитектуры, и вdocs/saas/,docs/trend-detection-future-roadmap.md: это остаток замысла, а не описание кода.Пороги и веса (
trend_cluster_stable_threshold,trend_cluster_emerging_threshold,signal_velocity_weight, …) читаются_cluster_settingsизadmin_runtime_settingsповерх дефолтов и меняются без пересборки — числа здесь не фиксируются.LLM трогает только stable-кластеры и только для текста брифа (
_enrich_stable_briefs). Дефолты проставляются до обращения к модели, поэтому при выключенномTREND_BRIEF_ENABLEDили сбое генерации строка всё равно пишется:insightполучает заготовку вида «N материалов из M источников»,opportunity— пустую строку,title_ruостаётсяNULL. Сбой самого обогащения ловится в_persist_signal_outputsи не роняет запись кластеров.Счётчики gap-анализа (
topics_generated,topics_dropped_by_*,searxng_errors,topics_kept) уезжают вcluster_runs.summary: по ним видно, кто виноват в нуле, без чтения логов дочернего процесса.
Реактивного пайплайна не существует. Прежняя редакция описывала «Reactive (cron 30 мин)» с
burst_scoreи записьюTrendCluster {pipeline: "reactive"}. Ни продюсера, ни крана в коде нет: словоreactiveвстречается только в трёх объявлениях типа (mcp/tools/search_trend_clusters.py,mcp/server.py,shared/models/trend_cluster.py), а_upsert_signalпишет литерал'stable'. В БД на 04.08.2026 — 393 строкиtrend_clusters, все сpipeline='stable', ни одной'reactive'за всё время с 28.03.2026. Параметрpipeline="reactive"у MCP-инструментаsearch_trend_clustersсинтаксически валиден и всегда вернёт пусто.
docker exec frontier-intelligence-postgres-1 psql -U frontier -d frontier \
-tAc "SELECT pipeline, count(*) FROM trend_clusters GROUP BY 1;"Проверка e2e после деплоя
Деплой. Код запечён в образ через
COPY, поэтомуrsync + restartего не обновляет — нужен rebuild иup -d --force-recreate. Пересобирать надо все сервисы со своимbuild:, а их девять, а не «worker и admin»; список не переписывай руками, а вытащи из compose:awk '/^ [a-z0-9_-]+:[[:space:]]*$/{s=$1; sub(":","",s)} /^ build:/{print s}' docker-compose.ymlНа 04.08.2026 это
gpt2giga-proxy, ingest, xray, worker, paddleocr, mcp, mcp-gateway, admin, crawl4ai— прежняя редакция перечисляла семь и терялаxrayсmcp-gateway.С рабочей машины:
.\scripts\push-and-remote-deploy.ps1 -Services worker,admin(параметр-Services— массив, дефолтworker, admin) или задача Cursor «Sync → Server + Rebuild». Только миграции —.\scripts\push-and-remote-migrate.ps1, на сервереbash scripts/server-apply-sql-migrations.sh. Существующая БД от одногоinit.sqlне обновляется.Логи. Маркеры, по которым видно живой конвейер (проверены на живых логах 04.08.2026):
docker logs --tail 400 frontier-intelligence-worker-1 2>&1 | grep "Enriched " docker logs --tail 400 frontier-intelligence-worker-1 2>&1 | grep "llm_task task=" docker logs --tail 400 frontier-intelligence-worker-1 2>&1 | grep "Vision done" docker logs --tail 400 frontier-intelligence-worker-1 2>&1 | grep "Reindex done" docker logs --tail 400 frontier-intelligence-crawl4ai-1 2>&1 | grep "Crawl enrichment saved"PostgreSQL. Контейнер
frontier-intelligence-postgres-1, роль и БД —frontier:docker exec frontier-intelligence-postgres-1 psql -U frontier -d frontier -tAc \ "SELECT kind, count(*) FROM post_enrichments GROUP BY 1 ORDER BY 2 DESC;" docker exec frontier-intelligence-postgres-1 psql -U frontier -d frontier -tAc \ "SELECT stage, status, started_at FROM cluster_runs ORDER BY started_at DESC LIMIT 5;"Ожидаемые
kind:concepts,tags,valence,crawl,vision.Qdrant. Точки появляются только для релевантных постов (
indexing_status.embedding_status='done'), не дляdropped. Crawl/vision-обогащение патчит уже существующую точку черезstream:posts:reindex. Оба добивочных скрипта требуют переменные окружения и зависимости стека, поэтому запускаются внутри контейнера, а не с хоста (в контейнере Python 3.11, в/opt/frontier-intelligence/.venvна сервере — 3.10, и.envтам не подхватывается):docker exec frontier-intelligence-worker-1 sh -c \ 'cd /app && python scripts/enqueue_reindex_enriched_posts.py --kind crawl --kind vision --limit 1000' docker exec frontier-intelligence-worker-1 sh -c \ 'cd /app && python scripts/sync_trend_clusters_to_qdrant.py --dry-run'Первый догоняет накопленные crawl/vision-обогащения, второй зеркалирует stable-кластеры в Qdrant (в штатном режиме это делается внутри анализа). У обоих есть
--dry-run— первым делом стоит посмотреть, сколько попадёт под раздачу:enqueue_*отдаёт{'matched': N, 'queued': 0},sync_*— JSON сrows/points_prepared.Redis. Команды — в разделе про стримы выше.
Admin API. Все эндпоинты живут под префиксом
/apiи, кроме/api/health,/api/auth/loginи вебхука Alertmanager, закрыты cookie/Basic-авторизацией:curl -sS -u admin:"$ADMIN_PASSWORD" http://127.0.0.1:8101/api/pipeline/stats curl -sS -u admin:"$ADMIN_PASSWORD" http://127.0.0.1:8101/api/pipeline/streams curl -sS -u admin:"$ADMIN_PASSWORD" -X POST http://127.0.0.1:8101/api/pipeline/reprocess/<post_id>POST /api/pipeline/reprocess/{post_id}сбрасываетindexing_statusи заново публикует событие, вытаскиваяmedia_urlsиз строки поста иlinked_urls— изcontent.
Без префикса
/apiAPI не отвечает — но и ошибку не показывает. Роутер подключён какapp.include_router(pipeline_router, prefix="/api/pipeline")(admin/backend/main.py), поэтомуGET /pipeline/statsне 404, а 200 с HTML админки (SPA-фолбэк на любой неизвестный GET), аPOST /pipeline/reprocess/{id}—405 Method Not Allowed. Проверено 04.08.2026. Ловушка в том, что200легко принять за работающий эндпоинт: если в ответе<!doctype html>вместо JSON — потерян/api. Голыйcurlпо правильному пути отвечает401 {"detail":"unauthorized"}— это ожидаемый ответ авторизации, а не поломка сервиса.
Telegram: обход блокировок
# Два слоя, автоматическая ротация при сбое
PROXY_CONFIGS = [
# MTProxy — встроен в протокол Telegram, сложнее заблокировать
{"type": ProxyType.MTPROTO,
"host": MTPROXY_HOST, "port": 443, "secret": MTPROXY_SECRET},
# SOCKS5 — WireGuard LXC на Proxmox (fallback)
{"type": ProxyType.SOCKS5,
"host": "10.0.0.1", "port": 1080},
]
# 2 аккаунта: ротация при FloodWaitError / SessionRevokedError
# iter_messages() — эффективнее get_messages() для Telegram API
# Redis negative cache: album_seen:{channel_id}:{grouped_id}, TTL 6ч
# Управление через Admin UI /sources:
# - добавить/сменить MTProxy (host, port, secret)
# - добавить/сменить SOCKS5
# - назначить источник на аккаунт [0|1]
# - статус аккаунтов, последняя активностьСтруктура репозитория
Полного дерева здесь больше нет намеренно. В git 37 записей верхнего уровня и 307 на двух уровнях вложенности — такое дерево не читают, а сверять его руками при каждом новом модуле никто не будет. Ниже карта «каталог → за что отвечает»; фактический состав берётся командой из конца раздела.
Прежнее ASCII-дерево врало по всем каталогам — если помните его, перепроверьте. Проверено 04.08.2026:
worker/gigachat_client.pyбыл подписан «LangChain GigaChat + EmbeddingsGigaR». Первая строка файла:"""GigaChat client using openai SDK directly (no langchain-openai proxies issues).""". LangChain не объявлен ни в одном Dockerfile и ни в одномrequirements*.txt. В рабочем коде стека слово встречается ровно один раз — в этой самой строке; ещё дважды в тестах (tests/stub_policy.pyглушитlangchain_coreиlangchain_openai,tests/test_neo4j_concepts.pyберёт «LangChain» просто как имя концепта в фикстуре графа). Сверка:git grep -i langchain.
mcp/mcp_gateway.pyбыл подписан «SSE gateway». Транспорт — Streamable HTTP (FastMCP, эндпоинт/mcp), это сказано в докстринге самого файла.
admin/frontend/был показан как единственныйindex.html. Это 16-строчный загрузчик Vue 3 SPA; код лежит вjs/,css/,vendor/. Старая однофайловая админка на 3527 строк лежит рядом какadmin/frontend-legacy.html: никуда не смонтирована и в git не добавлена — файл untracked, поэтому в выводеgit ls-filesего не будет.Отсутствовали целиком:
crawl4ai/,gpt2giga-proxy/,services/,tests/,docs/.
.cursor/rules/показан как часть репозитория. Его нет ни в git, ни на сервере — каталог исключён в.rsync-exclude(секция «Local AI/editor tooling»).Списки файлов внутри
mcp/tools,worker/services,worker/chainsиscripts/отставали в разы:scripts/был показан семью строками при 64 файлах в git.
Сервисы
Каждый — отдельный образ в docker-compose.yml. Код запечён в образ: правка Python требует
пересборки, rsync + restart её не подхватит.
каталог | ответственность |
| Сбор из источников. |
| Обогащение. |
| Два процесса из одного каталога. |
| Панель управления. |
| Веб-краулинг и обогащение ссылок. |
| Прокси OpenAI-совместимого API к GigaChat, порт 8090. |
| Вспомогательные сервисы со своими образами: |
Общий код, схемы, конфиги
каталог | ответственность |
| Общий слой ingest, worker, mcp, admin и crawl4ai. Не всеми: paddleocr, gpt2giga-proxy и процесс mcp-gateway не импортируют его вовсе, а из |
| Схема хранилищ, а не данные. |
| YAML-дефолты бутстрапа: |
Инфраструктура и обвязка
каталог | ответственность |
|
|
| Provisioning целиком: |
|
|
| 64 файла в git. Основные группы: сборка и развёртывание на сервере ( |
| pytest, 97 файлов в git: 93 штуки |
| 93 markdown-файла. Точка входа — |
| Рабочий каталог для выгрузок и черновиков. В git попадает только |
В корне, помимо этого: три compose-файла (docker-compose.yml, docker-compose.host-fixes.yml
для проблемных хостов, docker-compose.build-host-fix.yml для BuildKit c security.insecure),
пять .env*.example (базовый, balanced и три .env.mode.*), Makefile, pyproject.toml,
requirements-dev.txt и requirements-crawl4ai.txt. Единого способа объявлять зависимости нет:
worker, ingest, admin, mcp и gpt2giga-proxy перечисляют пакеты инлайн в pip install внутри
своего Dockerfile, crawl4ai ставит корневой requirements-crawl4ai.txt, а у paddleocr есть
собственный services/paddleocr/requirements.txt. Сверка — git grep -l 'pip install' -- '*Dockerfile*'.
Как получить актуальный состав
Источник правды — git, а он только на сервере (локальной копии .git нет):
# два уровня вложенности, только то, что реально в репозитории
ssh frontier-intelligence 'cd /opt/frontier-intelligence && git ls-files | cut -d/ -f1-2 | sort -u'Локально, без git:
Get-ChildItem D:\Workspace\frontier-intelligence -Depth 1 -Directory |
Where-Object { $_.FullName -notmatch '__pycache__|\.git|\.ruff|\.pytest' } |
ForEach-Object { $_.FullName.Replace("D:\Workspace\frontier-intelligence\", "") }Рабочая копия шире репозитория с обеих сторон. .claude/, .cursor/, .vscode/, .agents/,
CLAUDE.md, AGENTS.md перечислены и в .gitignore, и в .rsync-exclude: в git их нет и через
rsync они не ездят.
«Исключено из синхронизации» ≠ «на сервере отсутствует». Проверено 04.08.2026: в
/opt/frontier-intelligenceлежат.claude/,.agents/,CLAUDE.mdиAGENTS.md— попали туда до появления правил исключения и с тех пор просто не обновляются. Реально отсутствуют на сервере только.cursor/и.vscode/. Проверка:ssh frontier-intelligence 'cd /opt/frontier-intelligence && ls -d .claude .agents .cursor .vscode CLAUDE.md AGENTS.md 2>/dev/null'
Обратное тоже верно: searxng/settings.yml, sessions/, prometheus/textfile/ и .bak-снапшоты
существуют только на сервере. Полный перечень расхождений — в .rsync-exclude, у него
свой синтаксис (первое совпавшее правило побеждает, отмена задаётся + с пробелом,
а не !), и это описано в шапке самого файла.
Переменные окружения
Полная форма — в ../.env.example. В git хранить только example-файлы; реальные .env, Telethon sessions и SearXNG secret_key остаются на сервере.
# PostgreSQL
POSTGRES_DB=frontier
POSTGRES_USER=frontier
POSTGRES_PASSWORD=<server-only-secret>
# GigaChat
GIGACHAT_CREDENTIALS=<server-only-secret>
GIGACHAT_SCOPE=GIGACHAT_API_PERS
GIGACHAT_BASE_URL=https://gigachat.devices.sberbank.ru/api/v1
# False — как в telegram-assistant при MITM; True — прод с доверенным CA в контейнере
GIGACHAT_VERIFY_SSL_CERTS=False
GIGACHAT_EMBEDDINGS_MODEL=EmbeddingsGigaR
EMBED_DIM=2560
GIGACHAT_MODEL=GigaChat-2
GIGACHAT_MODEL_PRO=GigaChat-2-Pro
GIGACHAT_VISION_MODEL=GigaChat-2-Pro
# S3 / Cloud.ru
S3_ENDPOINT_URL=https://s3.cloud.ru
S3_BUCKET_NAME=<server-bucket>
S3_REGION=ru-central-1
S3_ACCESS_KEY_ID=<server-only-secret>
S3_SECRET_ACCESS_KEY=<server-only-secret>
# Telegram аккаунт 0
TG_API_ID_0=<server-only-id>
TG_API_HASH_0=<server-only-secret>
# Telegram аккаунт 1
TG_API_ID_1=
TG_API_HASH_1=
# ── Telethon → Telegram DC (SOCKS5 или MTProxy) ─────────────────────────────
# gpt2giga-proxy (порт 8090) — только HTTP-доступ к GigaChat для worker; в Telethon НЕ подставлять.
# Удобная одна строка: TG_SOCKS5=HOST:PORT:USER:PASS (или TG_PROXY_DSN=…)
# Либо поля: TG_PROXY_HOST / TG_PROXY_PORT / TG_PROXY_USER / TG_PROXY_PASS
# Либо WG_SOCKS_HOST / WG_SOCKS_PORT (+ USER/PASS при необходимости) — см. .env.example
# MTProxy (имеет приоритет над SOCKS5 в env, если заданы host+secret):
MTPROXY_HOST=
MTPROXY_PORT=443
MTPROXY_SECRET=
# Жёстко: не стартовать ingest без прокси в environment (proxy_config только в БД не считается):
# TG_REQUIRE_PROXY=1
# После смены прокси в .env пересоздать контейнер:
# docker compose --profile core --profile ingest up -d --force-recreate ingest
# В логах ingest ожидать «Telegram proxy: SOCKS5 …» или «MTProxy …», а не прямой DC без прокси.
# Neo4j
NEO4J_PASSWORD=<server-only-secret>
# Сервисы
MCP_PORT=8100
ADMIN_PORT=8101
GRAFANA_PASSWORD=<server-only-secret>
# SearXNG
SEARXNG_URL=http://searxng:8080
# searxng/settings.yml не коммитится; secret_key генерируется на сервере.Docker Compose профили
Профилей десять, и все 18 сервисов объявлены хотя бы под одним. COMPOSE_PROFILES
в серверном .env не задан, поэтому голый docker compose up -d поднимает ноль
сервисов, а docker compose logs mcp падает с обманчивым no such service: gpt2giga-proxy.
Без профилей работают только docker compose ps и docker compose exec.
Профиль | Сервисы |
| postgres, redis, qdrant, neo4j, gpt2giga-proxy |
| ingest (telegram, rss, habr, web, api) |
| xray (единственный egress наружу) |
| worker (enrichment, vision, тренды, сигналы) |
| crawl4ai |
| paddleocr |
| mcp (8100, loopback), mcp-gateway (8102) |
| admin (8101) |
| searxng (стартует и с профилем |
| prometheus, alertmanager, grafana, node-exporter |
Профилей
ragиanalyticsне существует. Они фигурировали вCLAUDE.mdиAGENTS.mdдо 04.08.2026; обе команды поднимали ноль сервисов.
Набор нельзя урезать произвольно. ingest, admin и crawl4ai объявляют
depends_on: xray, поэтому без профиля xray compose падает ещё до запуска:
service "ingest" depends on undefined service "xray": invalid compose project.
Именно на этом на 04.08.2026 ломались три из четырёх точек входа деплоя.
Рабочий набор:
export COMPOSE_PROFILES="core,ingest,xray,worker,crawl,paddleocr,mcp,admin,searxng,monitor"
docker compose up -dЭталон для сборки — scripts/server-build-stack.sh. Проверять валидность набора
до деплоя: COMPOSE_PROFILES=... docker compose config --services.
As-Built Runtime
Здесь была третья по счёту сводка фактической цепочки — после «Архитектуры» и
«Processing Pipeline». Она разошлась с обеими и с кодом: обещала GigaChat Vision
(vision давно ушёл на wormsoft), прямое имя коллекции frontier_docs вместо алиаса,
SSE gateway on 8102 (транспорт — Streamable HTTP) и неполный список MCP-инструментов.
Копию убрали намеренно: три описания одного конвейера расходятся быстрее, чем их успевают править. Источники правды теперь по одному на вопрос:
Что нужно | Где смотреть |
Как устроен конвейер, стадии и стримы | |
Общая схема сервисов | «Архитектура» выше |
Что работает, а что частично | «Статус реализации» выше |
Что запущено прямо сейчас |
|
Что не доделано, с доказательствами | |
Расхождения документации со стеком |
Референс (telegram-assistant)
Старый проект используется только как справка. Переносить код напрямую нельзя без адаптации под workspace_id, текущие Redis Streams, SQLAlchemy async и отсутствие Supabase/Caddy/JWT/QR-auth.
Что смотреть | Статус в Frontier Intelligence |
Telethon iter_messages, account rotation, proxy handling | Adapted in |
Vision/OCR ideas | Adapted in |
gpt2giga-proxy contour | Kept as local |
crawl4ai pattern | Kept as |
Album assembler phases | Reference only; current ingest collapses albums into post/media events |
Grafana album dashboard | Reference only; current dashboard is |
EmbeddingsGigaR dim=2560 | Current env is |
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityCmaintenanceA memory management system that enables AI assistants to store, search, and visualize persistent conversation contexts using a Neo4j graph database. It provides an MCP server for integration with Claude Desktop along with a web-based dashboard for managing relationship-based knowledge.Last updatedMIT
- Alicense-qualityAmaintenanceA temporal knowledge graph system that enables users to record and query architectural decisions, implementation patterns, and project failures. It integrates with Claude to provide hybrid search, timeline tracking, and automated knowledge gap detection using graph analysis.Last updated3MIT
- Alicense-qualityCmaintenanceMCP server for trend-pulse, an agentic trend intelligence platform that fetches and analyzes trending topics from 37 sources, provides search, historical data, and lifecycle prediction via 29 tools.Last updated53MIT
- Alicense-qualityCmaintenanceA personal knowledge base MCP server with semantic search, storing thoughts in PostgreSQL with pgvector embeddings and providing 8 tools for capture, search, browse, stats, relations, traces, and hydration.Last updatedApache 2.0
Related MCP Connectors
Agentic Reddit/HN buying-signal detection for Claude Code, Cursor, and Windsurf via MCP.
Cross-agent artifact workspace with provenance across Claude Code, Codex, Cursor, LangGraph.
User-owned memory for AI agents, Copilot, Claude, IDEs, CLIs, and chat apps over remote MCP.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ilyasni/frontier-intelligence'
If you have feedback or need assistance with the MCP directory API, please join our Discord server