Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e67186c08 | |||
| df2071d5ee | |||
| 1db3dd1b34 | |||
| 714a9ca785 | |||
| 7be30c7174 | |||
| 2499218c18 | |||
| c69d047f3c | |||
| 6308471b2a | |||
| f214eb2fae |
483
DESIGN-a2a-agents.md
Normal file
483
DESIGN-a2a-agents.md
Normal file
@@ -0,0 +1,483 @@
|
||||
# DESIGN — Agap Agent Platform v2.1: the agent algebra
|
||||
|
||||
Status: **v2.1, agreed with alvis 2026-07-21** (v2 `7be30c71` + hardening review)
|
||||
Owner: alvis · Written with Claude
|
||||
|
||||
One design, two axioms, one verb. Everything alvis asked for — per-model queues,
|
||||
quota parking, Hindsight reflect as an async task, the Claude Code loop as a task
|
||||
puller, semantic/tier/direct routing — falls out as a special case rather than a
|
||||
rule. This document is the reference; the Kanboard A2A tasks implement it.
|
||||
|
||||
---
|
||||
|
||||
## 0. Glossary
|
||||
|
||||
- **Task plane / "the fabric"** — the task-passing substrate connecting all
|
||||
agents: **Kanboard** (the durable task store and queue — for humans *and*
|
||||
agents) + **A2A protocol semantics** (submit/status/result, Agent Cards,
|
||||
context-by-reference) + the **conventions** on top (claim/lease, priorities,
|
||||
parking, trust-class routing). Not a deployable component; the collective name,
|
||||
the way "the network" names cables + IP + routing.
|
||||
- **Card** — an agent's self-description: capabilities, tier, cost class, trust
|
||||
class, availability. Maps 1:1 to an A2A Agent Card.
|
||||
- **Backbone** — the concrete LLM an agent currently uses for reasoning.
|
||||
- **Context ref** — a pointer (Hindsight bank id, git ref, KB task id, file
|
||||
path) passed *instead of* pasted content.
|
||||
- **fabric-keeper** — the janitor daemon owning time semantics (§6b): lease
|
||||
sweeps, deadlines, dead-letter, inbox digests. It never assigns work and
|
||||
never triggers work.
|
||||
|
||||
## 1. Why
|
||||
|
||||
Observed on the live stack:
|
||||
|
||||
- **Duplicated LLM spend** — an Adolf turn costs a ~32.8K-token reply call plus a
|
||||
~22.8K-token background Hindsight extraction; ~425 tokens are the conversation.
|
||||
- **Tool bloat** — one Adolf carries ~84 MCP tool schemas + ~26 built-in tools
|
||||
every turn (~22K tokens), relevant or not.
|
||||
- **Quota cliffs** — Kimi's flat window (~60 msgs/5h, ~300/wk measured) makes
|
||||
Adolf go dark with no degradation path and no way to park work.
|
||||
- **Hardcoded background cognition** — Hindsight reflect/consolidation call a
|
||||
fixed model directly: no scheduling, no priority, no quota awareness.
|
||||
- **No growth path** — the ambition is autonomous research agents, remote llama
|
||||
nodes, more GPUs, more agents.
|
||||
|
||||
## 2. The algebra
|
||||
|
||||
### Axiom 1 — everything that can receive work is an Agent
|
||||
|
||||
An agent is `(identity, Card, Policy, State)`. The Card advertises capabilities,
|
||||
**tier** (model strength it offers or needs), **cost class**, **trust class**
|
||||
(§5), and an **availability function a(t)**. Special cases:
|
||||
|
||||
| Agent | Persona | Memory | Card highlights |
|
||||
|---|---|---|---|
|
||||
| LLM endpoint (`kimi`, `gemma3:4b`, …) | trivial (identity) | none | tier, cost, quota-shaped a(t) |
|
||||
| **Adolf** | proactive auditor (SOUL.md) | Hindsight bank `adolf` | trusted; scoped core tools |
|
||||
| **claude-coder** (Claude Code loop) | implementer | session + repo | trusted; pulls complex coding tasks |
|
||||
| **Torgash** | marketplace analyst | own bank | sandboxed; marketplace tools only |
|
||||
| **researcher** | autonomous researcher | own bank | sandboxed/untrusted inputs; own KB project |
|
||||
| router | delegator | none | resolves constraints → agents |
|
||||
| **alvis (the human)** | — | — | trust=human; a(t)=waking hours; **inbox = KB "waiting-on-me"** |
|
||||
|
||||
The human being an agent is not a metaphor: approval gates, escalations and
|
||||
decisions are ordinary tasks submitted to his inbox. The KB column he already
|
||||
processes *is* that inbox.
|
||||
|
||||
### Axiom 2 — one verb
|
||||
|
||||
```
|
||||
submit(task, target) -> taskRef # await(taskRef) optional => sync
|
||||
task = (intent, context-refs, constraints, priority, deadline, provenance)
|
||||
target ∈ { agent-id # direct: “this backbone / this specialist”
|
||||
| constraint-set # tier/capability: “any large model with tools”
|
||||
| auto } # router decides by availability/quota/complexity
|
||||
```
|
||||
|
||||
Context travels **by reference, never by value** — the single most important
|
||||
efficiency rule for inter-agent communication (A2A context-passing practice).
|
||||
`sync` vs `async` is not a second mechanism: sync = submit + await.
|
||||
|
||||
**Transport rule.** Sync and async share the algebra but not the transport:
|
||||
**sync goes direct** — an A2A `message/send` RPC straight to the target agent's
|
||||
endpoint, journaled to KB afterwards; **async/durable goes through KB** and is
|
||||
drained by polling workers. KB polling must never sit on a sync path — a sync
|
||||
call may not inherit poll-interval latency.
|
||||
|
||||
### Completion vs verification
|
||||
|
||||
KB convention (native semantics, no new machinery): the **Done column =
|
||||
unverified completion** — the worker/agent finished and self-reported. **Closing
|
||||
the task = verified completion.** The producer never closes its own task; the
|
||||
submitter, a human, or (later) a reviewer-agent closes after checking the
|
||||
task's acceptance criteria. Lifecycle: … → done (unverified) → closed
|
||||
(verified). For code, the PR review is the verification; closing follows merge.
|
||||
|
||||
### Theorems — the old rules become consequences
|
||||
|
||||
1. **"Queues are per model, not per agent."** Every agent has an inbox, but
|
||||
queues *accumulate* only where a(t) or throughput binds — at scarce agents:
|
||||
model-agents and the human. Persona agents transform-and-delegate, so their
|
||||
inboxes stay near-empty. The v1 rule is the scarcity special case.
|
||||
2. **Quota lifecycles are shapes of a(t).** always-on: a(t)=1. quota-gated
|
||||
(Kimi): a(t)=0 when the window is spent — the queue **parks**, nothing fails,
|
||||
drains on reset. cost-gated: a(t)=0 past budget. on-demand (remote llama):
|
||||
a(t)=0 until woken. Four lifecycles, one function.
|
||||
|
||||
**Note on "cost":** in this lab the binding constraint is **quota and VRAM,
|
||||
not money** — see §3a. The cost-gated shape exists for the optional paid
|
||||
fallback only; it is not the normal case.
|
||||
3. **Hindsight reflect is just a submit** — `{intent: reflect, refs: bank+query,
|
||||
constraints: tier≥large}`, async. Same for consolidation (low priority).
|
||||
4. **Backbone swap is a constraint edit.** Persona agents name constraints, not
|
||||
endpoints; the backbone resolves per-submit. Adolf-on-Kimi today,
|
||||
Adolf-on-local tomorrow — no code change.
|
||||
5. **The Claude Code loop is an ordinary consumer** — an agent whose policy is
|
||||
"pull complex coding tasks from the fabric". It was never special.
|
||||
6. **For free:** escalation = re-submit with wider constraints (gated by policy,
|
||||
§5); approval = submit(…, target=alvis); proactivity/cron = delayed
|
||||
self-submission; the researcher = a low-priority self-submitting loop.
|
||||
|
||||
### Granularity rule
|
||||
|
||||
A **Task** is a durable work item with a lifecycle worth auditing. A single LLM
|
||||
completion inside an agent's turn is **not** a Task — it is an implementation
|
||||
detail, observable in Langfuse, invisible to Kanboard. This keeps the KB-literal
|
||||
fabric free of micro-churn by construction.
|
||||
|
||||
## 3. Planes
|
||||
|
||||
```
|
||||
┌─ Task plane (“the fabric”) ─────────────────────────────────────────┐
|
||||
│ Kanboard = the queue + audit + human inboxes (KB-LITERAL: no │
|
||||
│ separate store). A2A semantics; claim/lease; priorities; parking. │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
┌─ Agent plane ───────────────────────────────────────────────────────┐
|
||||
│ Registry of Cards (persona, memory bank, tool scope, trust class, │
|
||||
│ preferred tier, current backbone). Runtimes: OpenClaw (Adolf + │
|
||||
│ specialists), Claude Code CLI, thin workers. │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
┌─ Model plane ───────────────────────────────────────────────────────┐
|
||||
│ LiteLLM gateway (:4000). Auto Router v2 (2026-07-14) does the SYNC │
|
||||
│ routing natively: pinned model | tier pools | complexity/semantic │
|
||||
│ auto-routing (SIMPLE<MEDIUM<COMPLEX<REASONING), plus virtual keys, │
|
||||
│ budgets, 429-fallback. alvis's three routing modes map 1:1: │
|
||||
│ specific backbone → pinned model_name │
|
||||
│ “tier” routing → tier pool │
|
||||
│ automatic router → auto_router/complexity_router │
|
||||
│ Classification is EMBEDDING-based on the local bge-m3 (semantic- │
|
||||
│ router): no classifier LLM, no API spend (§3a). │
|
||||
│ Deployments: kimi (wrapper) | local small model | local embedder; │
|
||||
│ metered/paid = opt-in fallback only, never implicit. │
|
||||
│ The fabric owns everything LiteLLM cannot: ASYNC queueing, parking │
|
||||
│ across quota windows, leases, cross-agent quota/GPU arbitration. │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
┌─ Context stores ────────────────────────────────────────────────────┐
|
||||
│ Hindsight banks (per-agent memory) · gitea (code, docs, this file) │
|
||||
│ · KB task bodies · files. Tasks point here; payloads never inline. │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3a. Cost model — no metered API by default
|
||||
|
||||
**Hard scope constraint (alvis):** the workflow is **Claude Code + the Kimi
|
||||
wrapper + a local GPU embedder + a small weak local model**. We do **not** pay
|
||||
per-token for API usage. Consequences that shape the design:
|
||||
|
||||
| Resource | Nature | Constraint |
|
||||
|---|---|---|
|
||||
| **Claude Code** | flat subscription; a *runtime*, not a metered API | the `claude-coder` agent's capacity |
|
||||
| **Kimi** (via adolf-llm / hindsight-llm wrappers) | flat subscription, windowed | ~60 msgs/5h, ~300/wk — quota, not money |
|
||||
| **local small model** (ollama) | free | GPU/VRAM contention (§3b) |
|
||||
| **local embedder** (bge-m3) | free, already resident | never-evict |
|
||||
| paid API (Haiku/Flash/…) | metered | **optional fallback only — disabled by default, explicitly opt-in** |
|
||||
|
||||
So the "cheap tier" is the **local small model**, not a cheap paid model. The
|
||||
budget governor (§5) therefore arbitrates **quota and GPU**, not spend. Any
|
||||
paid deployment in the LiteLLM config must be explicitly enabled per agent via
|
||||
its virtual key; nothing routes to a metered model implicitly.
|
||||
|
||||
**Routing classification is embedding-based, not model-based.** LiteLLM's Auto
|
||||
Router (built on `semantic-router`) takes a configurable `embedding_model` +
|
||||
`match_threshold`, so complexity/semantic classification runs on the **local
|
||||
bge-m3** we already keep resident — no classifier LLM call, no API spend, ~zero
|
||||
marginal cost. (Auto Router v2 is new as of 2026-07-14 and has an open
|
||||
embedding-related bug report: verify hands-on and keep a heuristic
|
||||
keyword/length fallback for the classifier.)
|
||||
|
||||
### 3b. GPU residency — a local model's a(t) is not 1
|
||||
|
||||
Local "free" models contend for VRAM with interactive components (measured on
|
||||
the 8 GB GTX 1070: bge-m3 + gemma3:4b + tei-reranker ≈ 6.2 GB; loading anything
|
||||
bigger evicts the reranker and silently regresses recall latency). So a local
|
||||
model's availability is **a(t) = f(VRAM headroom)**, and the model registry
|
||||
carries a **residency policy**: a never-evict set (embedder, reranker —
|
||||
interactive-critical), allowed co-residency groups, and a pre-load check every
|
||||
worker must pass before pulling a model onto a GPU. With more GPUs this becomes
|
||||
a placement problem — same policy, more slots.
|
||||
|
||||
### Personas and Cards are code
|
||||
|
||||
SOUL.md files and agent Cards live **in git** and are deployed to runtimes —
|
||||
never edited live in volumes. "Who changed Adolf's soul" must be a `git log`
|
||||
answer. (The current SOUL.md in the adolf-state volume is migration debt.)
|
||||
|
||||
### Eval gate on backbone/routing changes
|
||||
|
||||
Backbone swap being "one constraint edit" is quality-blind. Each agent keeps a
|
||||
**golden set** (10–20 canonical exchanges); any backbone or routing change is
|
||||
shadow-replayed against it and compared (Langfuse datasets/evals) before taking
|
||||
effect. The algebra's flexibility must not become a silent-degradation machine.
|
||||
|
||||
## 4. A2A: the protocol, adopted now
|
||||
|
||||
The algebra maps 1:1 onto A2A v1.0 (Jan 2026), which is why we implement the
|
||||
real protocol immediately rather than "patterns first":
|
||||
|
||||
| Algebra | A2A |
|
||||
|---|---|
|
||||
| Card | Agent Card (`/.well-known/agent.json`) |
|
||||
| submit / await | `message/send` (sync-ish) / `tasks/get` (async) |
|
||||
| task lifecycle | submitted → working → input-required → completed/failed/canceled |
|
||||
| notify | push notifications |
|
||||
|
||||
Implementation: JSON-RPC 2.0 over HTTP on the LAN; each runtime (OpenClaw,
|
||||
Claude loop, workers) exposes/consumes A2A; Kanboard remains the durable state
|
||||
behind the endpoints. Scalability/extensibility later (remote nodes, third-party
|
||||
agents) then needs zero redesign.
|
||||
|
||||
**Auth is mandatory on every A2A surface.** The LAN is **not trusted** — the
|
||||
xray/3x-ui VPN terminates other people's peers on it. No unauthenticated
|
||||
JSON-RPC listener, ever: shared tokens minimum, mTLS preferred.
|
||||
|
||||
## 5. Trust & sandboxing
|
||||
|
||||
**Trust classes** (on every Card):
|
||||
|
||||
```
|
||||
human > trusted > sandboxed > untrusted
|
||||
```
|
||||
|
||||
- **trusted** (Adolf, claude-coder): vault access **yes**; outward actions per
|
||||
existing ask-first rules.
|
||||
- **sandboxed** (Torgash, researcher): **no vault**, no outward sends; scoped
|
||||
MCP allowlist; KB access **project-scoped** (researcher gets its own KB
|
||||
project(s)).
|
||||
- **untrusted** = anything ingesting the open web: its *outputs* are tainted.
|
||||
|
||||
**Taint / prompt-injection boundary:** tainted output may be written only to the
|
||||
agent's own bank/notes/project. Promotion into a trusted agent's memory or into
|
||||
any action requires a gate (initially: a task to alvis's inbox; later possibly a
|
||||
reviewer-agent).
|
||||
|
||||
**Escalation policy (initial): always-ask.** A task that fails on its tier is
|
||||
not silently retried on a bigger model; it becomes a decision task in alvis's
|
||||
inbox. Revisit once behavior is observed (debugging phase by design).
|
||||
|
||||
**Sandboxed coding — workspace lease:** per **task**, not per agent:
|
||||
`workspaces/<agent>/<task-id>/` = ephemeral gitea clone + branch; execution
|
||||
inside a container (no vault creds by default, network allowlist, resource
|
||||
caps); merge **only via PR** to gitea; autonomous agents never push to main.
|
||||
Reviewer = human, or later a reviewer-agent (just another persona).
|
||||
|
||||
**Global budget governor:** near the end of a quota window, interactive agents
|
||||
(Adolf) outrank background ones (researcher, consolidation) — arbitration lives
|
||||
in the fabric (priorities + a small governor rule), not in LiteLLM.
|
||||
|
||||
**Fabric hygiene (runaway protection):** agents submit tasks that cause agents
|
||||
to submit tasks — idempotency keys stop duplicates, not generative loops. So:
|
||||
per-agent **task-creation quotas**; an **ancestry depth cap** on provenance
|
||||
chains; cycle detection at submit; and a **dead-letter** state for poison tasks
|
||||
after max-retries — never an infinite retry loop through paid quota.
|
||||
|
||||
## 5b. Humans (plural) and memory partitioning
|
||||
|
||||
There is more than one human already (alvis and elizaveta are both on Adolf's
|
||||
Matrix allowlist) and there will be more. Every human is an agent with
|
||||
trust=human, their own inbox, and — critically — **their own privacy domain**.
|
||||
|
||||
**Memory partitioning (hard rules):**
|
||||
|
||||
- **Per-human private banks**: `adolf-alvis`, `adolf-elizaveta`, … Everything
|
||||
learned in conversation with human H goes to H's private bank by default.
|
||||
**Content from one human's conversations must never surface to another
|
||||
human.** This is a correctness property, not a preference.
|
||||
- **One shared household bank** for facts that are explicitly household-wide
|
||||
(addresses, devices, routines, shared plans). Trusted agents may write;
|
||||
**promotion from a private bank happens only by that human's explicit action
|
||||
or approval task** — never automatically.
|
||||
- **Recall is interlocutor-scoped**: when Adolf talks to H it recalls from H's
|
||||
private bank + the shared bank, nothing else. The recall/retain hooks select
|
||||
the bank by interlocutor identity.
|
||||
- Sandboxed agents (Torgash, researcher) read at most the shared bank; never
|
||||
any private bank. This is the cross-human face of the memory matrix.
|
||||
- The current single `adolf` bank is migration debt: split into
|
||||
`adolf-alvis` + shared.
|
||||
|
||||
**Human inbox design:** notifications are priority-routed — gate/urgent tasks
|
||||
ping the human via Matrix (Adolf initiates them; cf. proactive-messaging work),
|
||||
everything else lands in a daily digest from the fabric-keeper. Ignored gate
|
||||
tasks park and re-remind; **they never default-approve**. Vacation mode: a
|
||||
human's a(t)=0 parks their inbox like any other scarce queue — gated flows
|
||||
wait; predefined degraded defaults apply where explicitly configured.
|
||||
|
||||
## 6. Executor — thin KB-polling workers
|
||||
|
||||
No Temporal/Hatchet: at homelab scale (dozens of tasks/day) a durable-execution
|
||||
platform would duplicate Kanboard as a second source of truth. Instead, one
|
||||
small worker daemon per model-queue (compose services, ~200 lines, shared lib):
|
||||
|
||||
```
|
||||
loop:
|
||||
a(t) check # quota/budget/health probe; if 0 → park (sleep, re-probe)
|
||||
poll KB view # filtered: my queue, status=queued, by priority
|
||||
claim # atomic: assign-to-self + column move + lease timestamp
|
||||
resolve refs # fetch context by reference
|
||||
execute # via LiteLLM (model-agents) / agent runtime (persona)
|
||||
write result ref # to the shared store; never inline
|
||||
update status # done | failed(retry policy) | input-required(→ inbox)
|
||||
```
|
||||
|
||||
Leases + heartbeats make dead workers safe: an expired lease returns the task to
|
||||
queued. Two workers on one queue never double-run a task (claim is atomic).
|
||||
Idempotency keys on submission prevent duplicate proactive tasks. OpenClaw cron
|
||||
is the proactive *submitter* (Adolf's schedule); workers are the *drainers*.
|
||||
|
||||
### 6a. Task schema — the KB-literal mapping
|
||||
|
||||
Per the **KB-literal decision** (§10.2: *"Kanboard is the queue, humans
|
||||
included; no separate store"*): there is no separate task schema or task
|
||||
table anywhere — a Task (§2, Axiom 2: `(intent, context-refs, constraints,
|
||||
priority, deadline, provenance)`) is **entirely represented by native
|
||||
Kanboard task fields plus a small set of conventions layered on top**
|
||||
(columns, tags, comments). The table below is that mapping, field by field.
|
||||
It is not aspirational: every row is what `kb-claim` and `kb_worker.py`
|
||||
(`/home/alvis/kanboard/bin/`) already read or write today — those two files
|
||||
are the ground truth this table documents, not a separate spec to keep in
|
||||
sync by hand.
|
||||
|
||||
| Task field | Represented as | Notes |
|
||||
|---|---|---|
|
||||
| **id** | native Kanboard task `id` | Globally unique per Kanboard instance. A `taskRef` in the algebra (§2) is `(project_id, task_id)` — project scopes columns/tags, so the pair (not the bare id) is what a worker needs to act on a task. |
|
||||
| **intent** | `title` (short) + `description` (full spec, Markdown) | `description` is the literal task body — what to do, acceptance criteria, links. `kb-claim next`/`take` print it verbatim under `--- spec ---`; nothing paraphrases it. |
|
||||
| **required tier** | *derived*, not stored — from native `score` | `score` is Kanboard's complexity field (Fibonacci: 1,2,3,5,8,13,21), normally set by the human, settable via `create_task`/`update_task`. `kb-claim`'s `tier_for(score)` maps it: `0` (unrated) → `sonnet` (safe default, task should be flagged for rating, not silently run cheap) · `≤2` → `haiku` · `≤5` → `sonnet` · `>5` → `opus`. The **complexity gate** (kb/CLAUDE.md): an unrated task is tagged `blocked` with a comment asking for a score, instead of being dispatched on the default tier. |
|
||||
| **target queue** | Kanboard **project** | Each queue is a Kanboard project — e.g. the `Adolf` project is what the always-on-local and Kimi-quota worker configs point `"project"` at (`workers/*.example.json`). Trust-scoped agents get their own project (§5: researcher gets its own KB project). `swimlane_id` is read and carried through every claim/park/done move but is not yet used to subdivide a queue further — headroom for later without a new field. |
|
||||
| **priority** | native `priority` field, `0`–`3` | Take-order (kb/CLAUDE.md): `p1 > p2 > p0 > p3` (note `p0` sits between `p2` and `p3`, not below `p3`). `kb-claim`'s `eligible()` sorts candidates by `(-priority, board position)`; the interactive `/kb` loop layers a WIP-resume dimension on top (resuming WIP outranks fresh `p2`) — that extra ordering lives in the orchestrator convention, not in `kb-claim` itself. |
|
||||
| **status** | *derived* from column + tags + open/closed — no single status field | **Column** (`Backlog`→`Ready`→`Work in progress`→`Done`, looked up by title via `columns()`) is the coarse lifecycle stage. **Open vs closed** (`status_id`/`closeTask`) is unverified vs verified completion (§2 "Completion vs verification", decision log #15): Done+open = unverified (`kb-claim done`, never closes its own task); Done+closed = verified (`kb-claim close`, by someone other than the producer). **Tags** carry the remaining states: `blocked` = input-required / parked for a human (escalation, missing score, or a fabric-keeper deadline escalation — same tag, one park primitive); `dead-letter` = poison, retries exhausted (`kb-claim deadletter`); `needs-human-verify` = machine-verified but sensitive, left open for a human to close (`kb-claim escalate`). Maps onto the A2A lifecycle (§4): submitted≈Backlog/Ready, working≈WIP, input-required≈WIP+`blocked`, completed≈Done+closed, failed≈`dead-letter`. (`canceled` has no Kanboard convention yet — not in scope here.) Claim freshness (the lease) is a **comment**, not a field: `🔒 claimed by \`agent\` at <ts>` / `lease renewed by …`, parsed back out by `lease-status` via regex — this is what lets an expired lease be swept back to `Ready` without a dedicated lease column. |
|
||||
| **context refs** | plain text *inside* `description` (and follow-up `comment`s) — convention, not a typed field | Per Axiom 2, "context travels by reference, never by value": a bank id, git ref/SHA, another KB task id (`#N`, which Kanboard auto-links), or a filesystem path, written as text. Enforced by review/convention, consistent with KB-literal — there is no schema-validation layer sitting in front of Kanboard to enforce it mechanically. |
|
||||
| **result ref** | the `note` on the `done`/`park`/`deadletter` comment | `kb_worker.py`'s `Outcome.note` (in-process) is flushed by `report_outcome()` to a Kanboard comment (`✅ {note}` for done) via `kb-claim done --note …`. No separate result store: the comment thread *is* the audit trail (kb#159's "closing is verification" model reads it). |
|
||||
| **submitter** | native Kanboard `creator_id` | Set automatically by `createTask`; exists on every task already. Distinct from `owner_id`, which is the *current claimant* and is what `claim`/`park`/`done` mutate. Not yet read by `kb-claim`/`kb_worker.py`/`fabric-keeper.py` — available, unused, out of scope for this task. |
|
||||
| **deadline** | native Kanboard `date_due` field | Enforced by `fabric-keeper.py`'s `enforce_deadlines()` (§6b "time semantics"): a fabric-owned, open, unparked task with `date_due` in the past gets `kb-claim park`ed (tagged `blocked`, comment naming the responsible inbox = current owner, or alvis if unassigned). Idempotent — already-`blocked`/`dead-letter` tasks are skipped so re-sweeps don't spam. |
|
||||
|
||||
### 6b. Who is "the scheduler"? — decomposed, plus one janitor
|
||||
|
||||
There is deliberately **no central dispatcher**. Scheduling decomposes into
|
||||
four concerns, each with its own owner:
|
||||
|
||||
| Concern | Question | Owner |
|
||||
|---|---|---|
|
||||
| Triggering | when do tasks appear? | OpenClaw cron, agents' delayed self-submissions, humans |
|
||||
| Dispatch | which task runs next? | each queue's worker (claim by priority under its a(t)) |
|
||||
| Admission | may it run now? | LiteLLM budgets/rate + the budget governor |
|
||||
| **Time semantics** | expired leases, deadlines, stuck tasks? | **the fabric-keeper** |
|
||||
| Failure/anomaly response | something went wrong — now what? | Zabbix → the ops-agent (§6d) |
|
||||
|
||||
The **fabric-keeper** is one tiny always-on daemon that neither assigns nor
|
||||
triggers work. It sweeps expired leases back to queued, enforces task deadlines
|
||||
(escalating to the responsible inbox), moves poison tasks to dead-letter, emits
|
||||
the human daily digest, and exports queue depths/ages to Zabbix.
|
||||
|
||||
Its defining property is being **off the critical path**: kill it and work still
|
||||
flows (workers keep pulling and running) — only hygiene degrades. That is what
|
||||
separates a janitor from a scheduler, which in a push system would stop
|
||||
everything.
|
||||
|
||||
**Deliberately NOT a keeper duty: missed crons.** A cron window missed because
|
||||
the host was down is not a task-lifecycle event — it's an *operational fault*,
|
||||
and re-firing it would make the keeper a trigger. Instead: schedules that must
|
||||
not be missed are **monitored in Zabbix** (a missed run raises a warning like
|
||||
any other infra fault), and the response is handled per-incident, not by a
|
||||
blanket catch-up policy (§6d).
|
||||
|
||||
### 6d. Faults are incidents, not keeper chores
|
||||
|
||||
Anything that "went wrong" — a missed critical cron, a service down, a queue
|
||||
backing up, a stale backup — surfaces as a **Zabbix problem**. Zabbix is the
|
||||
single place operational faults are detected.
|
||||
|
||||
Response is **per-incident, not global**: each Zabbix problem class gets its own
|
||||
mitigation path, expressed as a task. Later this is automated by a dedicated
|
||||
**ops-agent** — a worker that watches Zabbix problems and, per problem class,
|
||||
either applies a known mitigation or files a task (to the right agent, or to a
|
||||
human inbox) with the incident as context. One blanket "catch-up policy" would
|
||||
be exactly the wrong abstraction: a missed briefing, a missed backup and a
|
||||
missed consolidation want completely different responses.
|
||||
|
||||
### 6c. Kanboard is tier-0 now
|
||||
|
||||
Promoting KB to the fabric's backbone promotes its ops class: **backups on par
|
||||
with the vault**, Zabbix monitoring of the service and API, and a defined
|
||||
**degraded mode** — if KB is down, Adolf still answers Matrix chat (no fabric
|
||||
operations, no task memory), workers park, nothing crashes or data-loses.
|
||||
|
||||
## 7. Observability — Langfuse (kept), wired for real
|
||||
|
||||
Decision: keep **Langfuse** (already deployed; best-in-class self-hosted:
|
||||
traces + per-token cost + prompt management + evals, MIT). Grafana rejected for
|
||||
this role — generic metrics with no LLM semantics (the source of past
|
||||
dissatisfaction); Zabbix keeps infra monitoring. To do (it currently receives
|
||||
nothing): LiteLLM success/failure callbacks → Langfuse; tag every trace with
|
||||
`agent`, `task-id`, `queue`; per-agent cost dashboards; upgrade v2→v3. Every
|
||||
completion is traced here — this is where sub-Task granularity lives.
|
||||
|
||||
## 8. Growing the lab
|
||||
|
||||
- **More GPUs / remote llama** → new model-agent Cards with `on-demand` a(t)
|
||||
(health probe, wake hook, graceful absence). Routing skips absent nodes.
|
||||
- **More specialists** → new Cards + scoped tools + own banks. The fabric and
|
||||
A2A don't change.
|
||||
- **Autonomous research agents** → low-priority loops on always-on local queues,
|
||||
escalating (via always-ask, initially) for large-model synthesis; own KB
|
||||
project; tainted outputs until promoted.
|
||||
|
||||
## 9. Migration order
|
||||
|
||||
1. Registries: model Cards + agent Cards (schema + populate).
|
||||
2. First thin worker end-to-end on an always-on local queue.
|
||||
3. Quota-gated worker (Kimi park/resume). Claim/lease semantics.
|
||||
4. A2A protocol surface (JSON-RPC + Agent Cards) over the fabric.
|
||||
5. Cutovers: Hindsight reflect → fabric; consolidation → fabric (low prio);
|
||||
claude-coder + Adolf declared as registry agents (Adolf's tools shrink to
|
||||
scoped core).
|
||||
6. Trust enforcement: capability grants (virtual keys + MCP allowlists), taint
|
||||
gate, budget governor, langfuse wiring.
|
||||
7. Scale: Torgash, researcher (own KB project), on-demand nodes.
|
||||
|
||||
## 10. Decision log (2026-07-21, alvis)
|
||||
|
||||
1. Single completions are not Tasks (langfuse-only) → no micro-churn.
|
||||
2. **KB-literal**: Kanboard is the queue, humans included; no separate store.
|
||||
3. Trust classes as §5; vault = trusted only.
|
||||
4. Escalation = always-ask initially.
|
||||
5. Researcher: KB access allowed, own project(s), scope-limited.
|
||||
6. Real A2A protocol now (JSON-RPC + Agent Cards).
|
||||
7. Proactive schedules: OpenClaw cron → fabric.
|
||||
8. Langfuse kept as the observability layer; Grafana rejected; Zabbix = infra.
|
||||
9. Executor = thin KB-polling workers; no Hatchet/Temporal at this scale.
|
||||
10. Sync routing = LiteLLM Auto Router v2; fabric owns async/parking.
|
||||
11. Sandbox = per-task workspace lease + container + PR-only merges.
|
||||
|
||||
Added in v2.1 (hardening review, same day):
|
||||
|
||||
12. **Multi-human**: per-human private banks + shared household bank;
|
||||
interlocutor-scoped recall; cross-human leakage forbidden (hard rule);
|
||||
promotion to shared only by the owning human's action/approval.
|
||||
13. A2A auth mandatory everywhere — the LAN is untrusted (VPN peers).
|
||||
14. KB = tier-0 infrastructure (backup, monitoring, degraded mode).
|
||||
15. Done column = unverified completion; closed task = verified; the producer
|
||||
never closes its own task.
|
||||
16. Scheduler = decomposed (cron/self-submission triggers; workers dispatch;
|
||||
LiteLLM+governor admit); the **fabric-keeper** janitor owns time semantics
|
||||
(leases, deadlines, dead-letter, digests) and neither assigns nor triggers.
|
||||
17. Personas/Cards live in git, deployed — never edited live.
|
||||
18. Backbone/routing changes gated by golden-set shadow eval (Langfuse).
|
||||
19. Fabric hygiene: creation quotas, ancestry depth cap, cycle detection,
|
||||
dead-letter for poison tasks.
|
||||
20. GPU residency policy: local a(t)=f(VRAM); never-evict set (embedder,
|
||||
reranker); pre-load checks.
|
||||
21. Transport: sync = direct A2A RPC (journaled); async = KB polling; polling
|
||||
never on a sync path.
|
||||
22. **Missed crons are not a keeper duty**: schedules that must not be missed
|
||||
are monitored in **Zabbix**; faults are incidents handled **per problem
|
||||
class** by a future **ops-agent** (§6d), never by a blanket catch-up policy.
|
||||
23. **No metered API by default** (§3a): scope = Claude Code + Kimi wrapper +
|
||||
local GPU embedder + small local model. The cheap tier is the *local*
|
||||
model, not a paid one; paid deployments are opt-in fallback per virtual
|
||||
key. The governor arbitrates **quota and GPU, not money**.
|
||||
24. **Routing classification is embedding-based** on the local bge-m3 via
|
||||
LiteLLM Auto Router (`semantic-router`): no classifier LLM call, no API
|
||||
spend. Keep a heuristic fallback — Auto Router v2 is new (2026-07-14) with
|
||||
an open embedding bug report.
|
||||
25. Hindsight gains an **optional caller-supplied cognition mode** (#131) so it
|
||||
can run inside a Claude Code session with zero extra API calls; the
|
||||
API/queue path stays the default. Same verb, different `target`.
|
||||
10
Dockerfile
10
Dockerfile
@@ -191,13 +191,21 @@ RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,shar
|
||||
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl git hostname lsof openssl procps python3 tini && \
|
||||
ca-certificates chromium curl git hostname lsof openssl procps python3 tini && \
|
||||
update-ca-certificates
|
||||
# chromium (Debian's real package, not snap) added for the OpenClaw `browser`
|
||||
# plugin — it resolves a system Chrome/Chromium to drive via CDP, and the
|
||||
# node:*-slim base ships none. Its shared libs (libnss3, libgbm1, ...) come in
|
||||
# as chromium's Depends. Adolf fork addition (kb#64: authenticated web access).
|
||||
|
||||
RUN chown node:node /app
|
||||
|
||||
COPY --from=runtime-assets --chown=node:node /app/dist ./dist
|
||||
COPY --from=runtime-assets --chown=node:node /app/node_modules ./node_modules
|
||||
# node_modules/@openclaw/ai is a workspace symlink -> ../../packages/ai; the
|
||||
# built lib must ship so the gateway's `import "@openclaw/ai"` resolves at
|
||||
# runtime (otherwise: "Cannot find package '@openclaw/ai'"). Adolf fork fix.
|
||||
COPY --from=runtime-assets --chown=node:node /app/packages/ai ./packages/ai
|
||||
COPY --from=runtime-assets --chown=node:node /app/package.json .
|
||||
COPY --from=runtime-assets --chown=node:node /app/pnpm-workspace.yaml .
|
||||
COPY --from=runtime-assets --chown=node:node /app/patches ./patches
|
||||
|
||||
184
docs/SPIKE-FINDINGS.md
Normal file
184
docs/SPIKE-FINDINGS.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# Adolf — P0 De-Risking Spike Findings
|
||||
|
||||
**Date:** 2026-07-05 · **Status:** Resolved — see verdicts below · **Scope:** ARCHITECTURE.md §4 gates 1-5
|
||||
|
||||
Method: live inspection of the running `kimi-agent` container/image (Dockerfile, `server.js`,
|
||||
`docker exec` into it), throwaway containers built from the same image with a **copy** of its
|
||||
`/root/.kimi-code` volume (created, tested, and fully destroyed — the live `kimi-agent` service was
|
||||
never stopped, restarted, or modified), decompiled/grepped `@moonshot-ai/kimi-code` bundle source,
|
||||
and direct reads of the vendored OpenClaw source already in this repo (`src/auto-reply/reply/*`,
|
||||
`docs/concepts/model-providers.md`) plus upstream web docs/search.
|
||||
|
||||
---
|
||||
|
||||
## Gate 1 — Kimi CLI package, home dir, MCP path, flags, auth persistence
|
||||
|
||||
**Verdict: architecture assumption was backwards. No change needed — current setup is already correct.**
|
||||
|
||||
- The running `kimi-agent` container uses `@moonshot-ai/kimi-code@0.22.3` (binary `kimi`), home dir
|
||||
`/root/.kimi-code`. This **is** the current upstream package — `MoonshotAI/kimi-code` is the
|
||||
active/maintained CLI; `MoonshotAI/kimi-cli` (home `~/.kimi/`) is the **legacy** predecessor being
|
||||
phased out. Confirmed both externally (web search: "Kimi CLI is evolving into Kimi Code CLI... the
|
||||
kimi-cli project will be gradually wound down") and internally — the shipped binary's own `kimi
|
||||
migrate` command copies data **from** `~/.kimi/` (legacy) **into** `~/.kimi-code/` (current), and its
|
||||
bundle contains the string `"Old data kept at ~/.kimi/ — kimi-cli still works."` So `~/.kimi/mcp.json`
|
||||
is the *old* path, not the upstream target.
|
||||
- **MCP config**: there is no `kimi mcp` subcommand and no `--mcp-config-file` flag in this version
|
||||
(`kimi --help` confirmed; grepped the bundle for the flag string — absent). MCP servers are
|
||||
configured via a **project-root `.mcp.json`** (Claude-Code-compatible schema, auto-discovered by
|
||||
walking up from cwd) or the interactive `/mcp-config` TUI command. Verified empirically: placing
|
||||
`.mcp.json` in a headless run's working directory does not error and `kimi doctor` validates config
|
||||
files cleanly from that directory. **Action for `adolf-llm`/`cognee-llm`: drop `shared-mcp.json` as
|
||||
`.mcp.json` into each session's working directory** rather than relying on a CLI flag that doesn't exist.
|
||||
- **Resume flags**: `-r`/`--resume` are **hidden, undocumented aliases for `-S`/`--session`** (confirmed
|
||||
by the CLI's own `session.resume_hint` meta line: `"command":"kimi -r session_..."`, and empirically
|
||||
by resuming a session with `-r <id>` and getting correct context recall). `-p`, `--output-format
|
||||
stream-json` both exist and work exactly as `kimi-agent/server.js` already uses them.
|
||||
- **Auth persistence in a fresh container/volume — confirmed YES.** Copied the live `kimi-agent`'s
|
||||
`/root/.kimi-code` into a brand-new named volume, mounted it on a throwaway container (different
|
||||
container, same image), and made a real API call (`kimi -p "..." --output-format stream-json`) —
|
||||
it authenticated and returned a real completion, and session resume (`-r <id>`) worked correctly
|
||||
across separate `docker exec` invocations. **Subscription auth is fully portable via the CLI-home
|
||||
volume** — `adolf-llm`/`cognee-llm` can each get their own volume seeded from (or sharing) the same
|
||||
OAuth credential files.
|
||||
- Cleanup: throwaway container, throwaway volume, and the host-side credential copy under `/tmp` were
|
||||
all deleted after testing. The live `kimi-agent` container was untouched throughout (never
|
||||
stopped/restarted/execed with anything destructive) and is still `Up` on its original uptime.
|
||||
|
||||
---
|
||||
|
||||
## Gate 2 — OpenClaw → provider session identity
|
||||
|
||||
**Verdict: no header/`user`-field wiring for custom providers — but OpenClaw gives us something
|
||||
better: a structured, parseable session-identity JSON block embedded directly in every request's
|
||||
prompt content. Use it as the primary session key; history-hash is not needed as a fallback for the
|
||||
common case.**
|
||||
|
||||
Read directly from the vendored OpenClaw source in this repo
|
||||
(`src/auto-reply/reply/inbound-meta.ts`) plus `docs/concepts/model-providers.md`:
|
||||
|
||||
- **Confirmed no hidden attribution headers or dynamic `user` field for custom `openai-completions`
|
||||
baseUrl providers.** `docs/concepts/model-providers.md:680`: *"Proxy-style OpenAI-compatible routes
|
||||
also skip native OpenAI-only request shaping: ... and no hidden OpenClaw attribution headers."*
|
||||
Attribution headers (`originator`, `version`, `User-Agent`) are attached **only** on verified native
|
||||
hosts (`api.openai.com`, `chatgpt.com/backend-api`) — `adolf-llm` will not receive them.
|
||||
- **The actual mechanism**: `buildInboundMetaSystemPrompt()` injects a stable
|
||||
`schema: "openclaw.inbound_meta.v2"` JSON block (`account_id`, `channel`, `provider`, `surface`,
|
||||
`chat_type`) into the **system prompt** of every turn — deliberately excluding anything that
|
||||
changes per-turn, to preserve provider-side prompt-cache prefix stability.
|
||||
- **The stable per-conversation key we need — `chat_id`** — is emitted by
|
||||
`buildInboundUserContextPrefix()` into the **user-role** message content instead, specifically as a
|
||||
`"Conversation info (untrusted metadata):"` JSON code block containing
|
||||
`chat_id: ctx.OriginatingTo` (plus `message_id`, `sender`, `timestamp`, etc.). `OriginatingTo` is a
|
||||
channel-agnostic routing id (e.g. `"whatsapp:+1555..."`, `"telegram:chat-1"`; for us it will be the
|
||||
Matrix room/peer id) and is confirmed present for direct-message channels that aren't `webchat`
|
||||
(Matrix qualifies) via `shouldIncludeConversationInfo = !isDirect || (channel && channel !== "webchat")`.
|
||||
This is populated by the core reply pipeline (`get-reply-run.*`) for **every** provider, native or
|
||||
custom — it's provider-agnostic.
|
||||
- **Action for `adolf-llm`**: on each incoming request, scan the latest user message content for the
|
||||
`"Conversation info (untrusted metadata):"` JSON block, parse `chat_id`, and use it directly as the
|
||||
session-map key → 1:1 Kimi session resume. This is strictly more robust than a history hash (it's
|
||||
stable even across edited/truncated history) and needs no OpenClaw-side config changes. Keep the
|
||||
history-hash approach only as a defensive fallback if the block is ever absent (e.g. webchat surface,
|
||||
or a future OpenClaw version relocates the field — grep on the label string, don't assume a fixed
|
||||
line offset).
|
||||
- Escape hatch confirmed but not needed here: `agents.defaults.models["provider/model"].params.extra_body`
|
||||
can merge static extra JSON into the outbound body for vendor-specific fields, but it's a config-time
|
||||
value, not a per-request dynamic session id, so it doesn't help with mapping.
|
||||
|
||||
---
|
||||
|
||||
## Gate 3 — Headless image input via file-path reference
|
||||
|
||||
**Verdict: confirmed working end-to-end.** No CLI flag is needed — the agent has a built-in
|
||||
`ReadMediaFile` tool it invokes autonomously.
|
||||
|
||||
Empirical test in a throwaway authed container: prompted `kimi -p "Look at ./test2.png and tell me its
|
||||
exact pixel dimensions and dominant color."` (a real 64×64 solid-color PNG placed in the cwd). Observed
|
||||
in the `stream-json` output:
|
||||
1. `{"role":"assistant","tool_calls":[{"function":{"name":"ReadMediaFile","arguments":"{\"path\":\"./test2.png\"}"}}]}`
|
||||
2. Tool result: reads the file, reports `Original dimensions: 64x64 pixels`, and returns an
|
||||
`image_url` content part with a base64 `data:image/png;base64,...` payload wired into the model context.
|
||||
3. Final assistant answer correctly identified the exact color used (`rgb(200, 30, 30)`).
|
||||
|
||||
(A first attempt with a degenerate 1×1 pixel PNG failed at the provider with `400 ... failed to decode
|
||||
image: invalid or unsupported image format` — that's the upstream vision API rejecting a
|
||||
near-empty test fixture, not a CLI/tool-path limitation; the 64×64 real PNG round-tripped cleanly.)
|
||||
|
||||
**Action for `adolf-llm`**: persist inbound Matrix images to the session's working directory and
|
||||
reference them by relative path in the prompt text (e.g. "See attached image: `./img_3.jpg`") — the CLI
|
||||
will autonomously call `ReadMediaFile` on it. No special flag or placeholder syntax
|
||||
(`[image #N ...]`, which is TUI-paste-only) is required for headless mode.
|
||||
|
||||
---
|
||||
|
||||
## Gate 4 — Graph store: Kuzu vs Neo4j for Cognee
|
||||
|
||||
**Verdict: Kuzu (embedded). Use it; do not stand up Neo4j.**
|
||||
|
||||
- **Kuzu is Cognee's own default graph backend** (`GRAPH_DATABASE_PROVIDER=kuzu`), file-based, runs
|
||||
embedded in-process with no network setup, no extra container, no auth surface, and stores its data
|
||||
as plain files (fits directly under `/mnt/ssd/dbs/cognee/`, consistent with the rest of Adolf's
|
||||
storage layout).
|
||||
- Cognee's own guidance: Kuzu is recommended for local/single-user use; it uses file-based locking and
|
||||
is explicitly **not** meant for multi-agent/concurrent-process access — that's when Cognee's docs
|
||||
point to Neo4j instead.
|
||||
- Adolf is explicitly single-user, single-agent, home-server scale (§1: "auto + tool" memory, one
|
||||
Matrix bot). Neo4j would add: a whole extra JVM-based service (~0.5-1GB+ RAM), its own
|
||||
backup/upgrade/security surface, and a network-exposed port — for zero benefit at this scale, since
|
||||
we don't need Cypher-browser visualization or concurrent multi-writer access.
|
||||
- **Recommendation**: Kuzu embedded, no separate container. Revisit only if/when a second concurrent
|
||||
agent needs to write to the same graph, or ad-hoc Cypher-browser graph exploration becomes a real
|
||||
requirement — neither applies to the current design.
|
||||
|
||||
---
|
||||
|
||||
## Gate 5 — `cognee-llm` suitability: agentic Kimi CLI vs LiteLLM fallback for batch cognify
|
||||
|
||||
**Verdict: default cognee's LLM backend to LiteLLM (fallback plan in ARCHITECTURE.md §3.3); keep
|
||||
`cognee-llm`/Kimi CLI as an optional low-volume path only. JSON reliability is fine; per-call latency
|
||||
and subscription/concurrency risk are the real blockers for batch use.**
|
||||
|
||||
Empirical test in a throwaway authed container (same one used for gate 1, cleaned up after):
|
||||
|
||||
| Call | Wall time | JSON cleanliness |
|
||||
|---|---|---|
|
||||
| Trivial prompt (`{"ok":true}`) — process floor | **~5.1 s** | clean |
|
||||
| Structured entity/relationship extraction, inline text (no tool call) | **~21.9 s** | clean, exact schema match |
|
||||
| Same extraction via file reference (adds a `Read` tool round-trip) | **~24.0 s** | clean, exact schema match |
|
||||
|
||||
Findings:
|
||||
- **JSON reliability is good** — in both extraction runs the final assistant `content` was strict,
|
||||
schema-conformant JSON with no prose/markdown fences, when explicitly instructed. This de-risks the
|
||||
"won't emit clean JSON" half of the concern.
|
||||
- **Latency is the real problem.** ~5 s is the CLI's fixed per-invocation floor (Node process cold
|
||||
start, config/credential load, provider round-trip) even for a near-empty completion; a realistic
|
||||
extraction call runs ~20-25 s. Cognify issues one such call per chunk/entity-extraction step — for a
|
||||
batch of even a few dozen chunks, serialized wall time reaches many minutes, and true parallelism is
|
||||
unverified: the Kimi subscription is a single-seat, interactive-coding-oriented plan, and hammering it
|
||||
with concurrent batch CLI invocations risks rate-limiting or provider-side throttling that this spike
|
||||
did not (and should not) test at scale — that's a live-account risk, not something to probe casually.
|
||||
- **Every call is also agentic** (tool-call round trips are possible/likely even for "just extract
|
||||
JSON" prompts, as seen in the file-reference test), adding non-determinism to latency and requiring
|
||||
the wrapper to reliably strip tool-call/meta lines from `stream-json` output before parsing the final
|
||||
JSON payload — extra parsing complexity for zero benefit in a task that doesn't need agentic tool use.
|
||||
- **Recommendation**: point Cognee's `LLM_API_BASE` at a LiteLLM-routed model (`judge`/local qwen, per
|
||||
ARCHITECTURE.md §3.3's own stated fallback) as the default for cognify. This is materially faster (no
|
||||
process-spawn floor, no agent loop, no tool-call risk) and avoids risking the shared subscription
|
||||
under batch load. Keep the `cognee-llm` Kimi-CLI wrapper buildable/available for low-volume or
|
||||
experimental use, but do not make it the default path.
|
||||
|
||||
---
|
||||
|
||||
## Summary table
|
||||
|
||||
| Gate | Verdict |
|
||||
|---|---|
|
||||
| 1. Kimi CLI package/home/MCP/auth | `@moonshot-ai/kimi-code` + `/root/.kimi-code` **is already current upstream** (kimi-cli/`~/.kimi/` is legacy). No `--mcp-config-file`/`kimi mcp`; use project-root `.mcp.json` instead. `-r`/`-p`/`--output-format stream-json` all confirmed. Subscription auth **persists** across a fresh container given a copied CLI-home volume. |
|
||||
| 2. OpenClaw session identity | No header/`user`-field wiring for custom providers. Use the `chat_id` (`ctx.OriginatingTo`) embedded in every turn's user-content "Conversation info" JSON block as the session key — parse it from prompt content, not headers. |
|
||||
| 3. Headless image input | Confirmed working: CLI autonomously calls a built-in `ReadMediaFile` tool on a referenced file path in `-p` mode; no flag/placeholder syntax needed. |
|
||||
| 4. Graph store | **Kuzu (embedded)** — Cognee's own default, matches single-user/no-extra-service goal. Neo4j not justified at this scale. |
|
||||
| 5. cognee-llm suitability | JSON output is clean, but ~5-24 s per call plus unverified batch-concurrency limits on a single-seat subscription make it unsuitable as the default. **Default to LiteLLM fallback** for cognify; keep Kimi-CLI path optional. |
|
||||
|
||||
All gates are resolved with either direct empirical verification (1, 3, 5) or authoritative source
|
||||
reading (2, plus corroborating docs for 4). No gate required an unresolved forbidden action.
|
||||
Reference in New Issue
Block a user