Files
adolf/docs/ARCHITECTURE.md
2026-07-05 06:10:13 +00:00

8.3 KiB

Adolf — Solution Architecture

Status: Proposed · Date: 2026-07-05 · Owner: alvis

Adolf is a self-hosted personal AI assistant on Agap, reachable first over Matrix (@bot:mtx.alogins.net). It is built on OpenClaw (the multi-channel assistant shell), driven by the Kimi Code CLI on the existing flat kimi login subscription (no per-token cost), with Cognee as long-term memory.


1. Goals & decisions

  • Base = OpenClaw (github.com/openclaw/openclaw, Node.js). Provides the multi-channel inbox, session/agent routing, SOUL.md persona, tool/MCP registry, and later WebChat + voice.
  • Brain = Kimi Code CLI subscription, hybrid. The CLI owns the agent loop, but OpenClaw's tools/MCP are not bypassed — they are exposed to the CLI as a shared MCP layer.
  • Memory = Cognee, "auto + tool". Auto-ingest every exchange and inject retrieved memory each turn, and expose explicit memory_search/memory_add MCP tools.
  • Cognee's backend LLM also runs on the Kimi subscription — via its own separate wrapper (cognee-llm), because its usage (stateless, structured, batch, no media/streaming) is unlike OpenClaw's. Embeddings stay on LiteLLM (embedder = nomic-embed).
  • The existing kimi-agent service is left untouched (still used for OpenWebUI experiments); we build new, separate wrappers.
  • First surface = Matrix chat only (voice/pipecat, WebChat, OpenWebUI deferred).

Why the hybrid works (de-risked)

OpenClaw runs its own agent loop and expects a raw LLM backend (provider api: openai-completions or anthropic-messages, configured with baseUrl/apiKey). The Kimi CLI is itself an agent, so naively wrapping it would bypass OpenClaw's tools. Confirmed escape hatch: Kimi Code CLI supports MCP (~/.kimi/mcp.json, headless --mcp-config-file <file>, kimi mcp add/list/remove/auth) plus image/video input, session resume (-r), headless -p, and --output-format stream-json. We point the CLI at the same MCP servers OpenClaw uses → the CLI can call OpenClaw/cognee tools while OpenClaw stays the channel/session/persona/memory layer.


2. Topology & data flow

Matrix ⇄ OpenClaw (Gateway: inbox, session routing, SOUL.md "Adolf")
             │  OpenAI /v1/chat/completions (streaming, images)   model = "adolf"
             ▼
      adolf-llm  (NEW wrapper container, :8010)
        • maps OpenClaw session → Kimi session (1:1 resume)
        • cognee.search() → inject memories into prompt   (auto-retrieve)
        • spawn: kimi -r <sess> -p <prompt> --output-format stream-json
                      --mcp-config-file <shared-mcp.json>
        • stream stream-json → SSE deltas (real streaming)
        • persist inbound images to session dir, reference by path (media)
        • after turn: cognee.add(user+assistant)             (auto-ingest)
             │                         ▲ MCP
             ▼                         │
        kimi CLI (agent) ── MCP ──► shared MCP servers:
                                     • cognee-mcp  (memory_search / add / cognify)
                                     • openclaw-tools bridge (OpenClaw integrations)

  cognee ──► cognee-llm  (SEPARATE wrapper, :8011)
               • stateless one-shot: kimi -p (fresh disposable session per call)
               • NO resume, NO media, NO MCP, NON-streaming, structured/JSON out
               • high concurrency for batch cognify
             ──► embeddings: LiteLLM `embedder` (nomic-embed), NOT the CLI

Reused infra (all in agap_git/openai/docker-compose.yml): Qdrant :6333 (cognee vectors), LiteLLM :4000 (embeddings + optional observability + fallback LLM), Langfuse, Postgres.


3. Components

3.1 OpenClaw ("Adolf") — the shell

Node 24 (≥22.19). Cloned into this repo. Configuration:

  • Model provider (custom): { baseUrl: "http://adolf-llm:8010/v1", apiKey: "${ADOLF_KEY}", api: "openai-completions", models: [{ id: "adolf", input: ["text","image"] }] }; default agent model adolf.
  • SOUL.md persona "Adolf" (Russian-friendly, concise).
  • Matrix channel with bot creds from Vaultwarden (MATRIX_ADOLF_PASSWORD / MATRIX_ADOLF_TOKEN); route room/peer → the Adolf agent.
  • MCP registry = cognee-mcp + the openclaw-tools bridge; the same server list feeds shared-mcp.json (single source of truth).

3.2 adolf-llm — conversational wrapper (:8010)

New agap_git/openai/adolf-llm/ (Dockerfile + server.js), own CLI-home volume + conversations workspace. OpenAI-compatible GET /v1/models + POST /v1/chat/completions (model adolf). Reuses proven bits from agap_git/openai/kimi-agent/server.js (session map w/ prune, convTurns/ textOf, runKimi spawn + stream-json parse, resume). Adds:

  • Real streaming — parse stream-json assistant deltas → OpenAI chat.completion.chunk SSE.
  • Media — persist image_url/base64 parts to the session dir, reference file paths in the prompt.
  • Session correspondence — key the map on OpenClaw's session id (not a history hash) → 1:1 kimi -r resume → Moonshot context-cache hits → token savings. Fallback: improved history-hash.
  • Shared MCP--mcp-config-file shared-mcp.json so the CLI can call OpenClaw/cognee tools.
  • Cognee hooks — pre-turn cognee.search() inject; post-turn async cognee.add().

3.3 cognee-llm — stateless wrapper (:8011)

New agap_git/openai/cognee-llm/, own disposable workspace + CLI-home volume (same subscription). Shares the runKimi/stream-json core but with the opposite policy: stateless one-shot (kimi -p, fresh temp dir per call, no resume), non-streaming, no media, no MCP, structured/JSON-oriented, low temperature, bounded parallelism for batch cognify. Cognee's LLM_API_BASEhttp://cognee-llm:8011/v1.

⚠️ Risk: cognify issues many structured calls; the agentic CLI adds per-call latency and may not reliably emit clean JSON. Fallback: route cognee's LLM to a LiteLLM model (judge/local qwen) while keeping Kimi for the assistant. Decide after a batch cognify test.

3.4 Cognee memory service

cognee + cognee-mcp containers. Backends: Qdrant (vectors), graph store = Kuzu (embedded) or Neo4j (decision), LLM via cognee-llm, embeddings via LiteLLM embedder. Persist under /mnt/ssd/dbs/cognee/. cognee-mcp exposes memory_search/memory_add/cognify into shared-mcp.json. Memories scoped per OpenClaw user/session.

3.5 Shared MCP layer ("smart tool integration")

shared-mcp.json is the contract both consumers (OpenClaw + Kimi CLI) use. Cognee-mcp is off-the-shelf. The openclaw-tools bridge exposes the OpenClaw integrations Adolf needs (send-to-channel, cron/reminders, browser/nodes) as MCP tools. Start minimal, grow.


4. Open gates to verify early

  1. Kimi CLI home/MCP path & package — current container uses @moonshot-ai/kimi-code + /root/.kimi-code; upstream is now MoonshotAI/kimi-cli + ~/.kimi/mcp.json. Confirm package, home dir, --mcp-config-file, and that kimi login subscription auth persists in the new containers' CLI-home volumes.
  2. OpenClaw → provider session identity — does OpenClaw forward a stable session id to a custom provider (header / user / metadata)? Decides robust mapping vs. hash fallback.
  3. Headless image input — confirm the CLI accepts images in -p runs via file-path references.
  4. Graph store — Kuzu embedded vs Neo4j service for cognee.
  5. Cognee-llm suitability — batch cognify latency/JSON reliability via the CLI vs LiteLLM fallback.

5. End-to-end verification

  • Matrix message → OpenClaw → adolf-llmkimistreamed reply token-by-token in Matrix.
  • Send an image on Matrix → CLI reads it (describe-the-image test).
  • Follow-up → same Kimi session resumed (sessions.json maps to one session; reduced prompt tokens vs. cold turn).
  • Memory — a stated fact is recalled in a later session via memory_search; cognee graph/Qdrant populated (auto-ingest); cognify's LLM served by cognee-llm (or LiteLLM fallback).
  • MCP tool — a tool call works mid-turn (e.g. set a reminder via the bridge).
  • The old kimi-agent service is unchanged and still serves OpenWebUI.