Архитектура

Обзор архитектуры

Как Cradle разделяется на хост-независимое ядро с двумя точками входа — headless-сервер и desktop-приложение — и как сообщение проходит от начала до конца.

Cradle — это одно хост-независимое ядро с двумя способами запуска: как headless-демон cradle-server или встроенный в Electron desktop app. Ядро никогда не импортирует Electron; каждая точка входа предоставляет HostAdapter до первого вызова кода ядра.

Две точки входа, одно ядро

              ┌──────────────── core (host-agnostic) ───────────────┐
              │  · DB (better-sqlite3 + sqlite-vec) + migrations     │
              │  · Triage orchestrator, router, agent-service        │
              │  · Risk system (L1 rules + L2 classifier)            │
              │  · llama.cpp runners (chat + embedding)              │
              │  · Channels: api / telegram / widget                │
              │  · RAG: chunker / ingest / retrieve / parsers        │
              │  · Auth: api_keys + scopes + audit                  │
              └──────────────────────────────────────────────────────┘
                  ▲                                        ▲
   ElectronHostAdapter                           NodeHostAdapter
   (app.getPath, dialog,                         (~/.cradle, EventEmitter,
    BrowserWindow.send)                           JSON stdout logger)
                  │                                        │
        src/electron/  (BrowserWindow + React UI)   src/server/  (HTTP API, no GUI)

Контракт HostAdapter абстрагирует всё платформенно-специфичное: директорию пользовательских данных, временную директорию, пути к ресурсам, доставку событий, выбор файлов и логирование. Desktop-приложение реализует его через API Electron; сервер реализует его через ~/.cradle, EventEmitter и структурированный JSON-логгер (pickFile в headless-режиме просто бросает исключение).

Сервер против desktop-приложения

Аспектcradle-serverElectron app
Запускnode dist/server/index.jsElectron Forge + Vite, BrowserWindow
Директория данных~/.cradle/OS user-data dir
Аутентификацияapi_keys + scopesТа же таблица; admin-ключ bootstrap
Выбор файловБросает (headless)dialog.showOpenDialog
Доставка событийEventEmitter (SSE)webContents.send в renderer

Поток сообщения от начала до конца

Каждое входящее сообщение проходит через одну и ту же упряжь независимо от точки входа:

Incoming → ChatChannel (telegram | widget | api)
  → unified IncomingMessage
  → persist message + upsert ticket
  → TriageOrchestrator.processIncoming
      ├── RiskAssessor.layer1(rules)      → triggered rules + level
      ├── RiskAssessor.layer2(classifier) → { category, risk, confidence,
      │                                       reasoning, requiredSkills }
      ├── Router.pick(category, skills)   → Agent
      ├── if agent has knowledge bases:
      │     RAG.search(text, { kbIds, topK }) → source snippets
      ├── AgentService.executeAgent(agent, turns, { sources })
      │     → builds the prompt (system + language + sources + turn)
      │     → runner.generate (streamed) → draft
      ├── save messages + risk_assessment
      └── decide by final level:
          ├── green         → send automatically
          └── yellow | red  → ticket waits for operator
                              → Inbox → approve / edit / reject → send

Ключевое свойство: вердикт red или yellow никогда не уходит автоматически. Он становится тикетом в Inbox оператора, и человек одобряет, редактирует или отклоняет черновик, прежде чем он достигнет канала.

Внутри ядра

  • Triageorchestrator (управляет потоком), router (выбирает агента по роли + пересечению навыков), agent-service (сборка промпта + выполнение) и подсистема risk (детерминированные правила, LLM-классификатор, категории, навыки).
  • LLM — интерфейс ModelRunner, реализованный LlamaCppRunner (обёртка над node-llama-cpp), менеджер моделей (докачка с проверкой SHA-256) и реестр моделей с LRU-эвикцией и ленивой загрузкой.
  • RAG — paragraph-first chunker, идемпотентное batched-ингестирование, top-k retrieval через sqlite-vec, парсеры Markdown, TXT, PDF, DOCX, XLSX и веб-краулер.
  • Channels — общий интерфейс ChatChannel с Telegram (grammY), встраиваемым виджетом (Fastify + WebSocket) и прямым HTTP API, всё через channel manager.

Desktop renderer

Electron renderer — это React + Zustand приложение со страницами Dashboard, Inbox, Ticket detail, Agents, Models, Knowledge, Integrations, Risk & Triage и Settings — человеческая сторона упряжи, где операторы ревьюят и одобряют.

Глубже: models и rag.