Files
adolf/DESIGN-a2a-agents.md
alvis 2499218c18
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CodeQL Critical Quality / Select Critical Quality shards (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (core-auth-secrets) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (config-boundary) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (gateway-runtime-boundary) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (channel-runtime-boundary) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (network-runtime-boundary) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (agent-runtime-boundary) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (mcp-process-runtime-boundary) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (memory-runtime-boundary) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (session-diagnostics-boundary) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (plugin-sdk-reply-runtime) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (provider-runtime-boundary) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (ui-control-plane) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (web-media-runtime-boundary) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (plugin-boundary) (push) Has been cancelled
CodeQL Critical Quality / Critical Quality (plugin-sdk-package-contract) (push) Has been cancelled
CodeQL Android Critical Security / Critical Security (android) (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
docs: add Agap agent platform design (A2A, per-model queues, agents)
Overall design for moving from a single tool-heavy Adolf to a multi-agent
platform: agents as personas (prompt+memory+tools) that can swap backbones,
models as the scarce queued resource (per-model queues with always-on /
quota-gated / cost-gated / on-demand lifecycles), and A2A patterns
(agent cards, task lifecycle, context-by-reference) to move work between them.

Covers the required cases: Hindsight reflect as an async queued task to the
large model, the Claude Code loop as just another queue-consuming agent, why
queues are keyed by model rather than agent, and how the lab grows to remote
llama / extra GPUs / research agents.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t8Qg9gi7H7HtT8MncoXAB
2026-07-21 06:03:29 +00:00

9.6 KiB

DESIGN — Agap Agent Platform (A2A, model queues, agents)

Status: draft for review · Owner: alvis · Drafted 2026-07-21

This is the overall design for turning the Agap homelab from "one Adolf carrying every tool + a few background LLM calls" into a multi-agent platform: agents as personas, models as queued compute, and A2A as the way work moves between them.


1. Why

Current pain, all observed on the live stack:

  • Duplicated LLM spend. Every Adolf turn costs two Kimi calls: the reply (~32.8K tokens in) and a separate background Hindsight retain/extraction (~22.8K). Only ~425 tokens of that is the actual conversation.
  • Tool bloat. One Adolf carries ~84 MCP tool schemas (~12K tokens) + ~26 built-in Kimi tools (~10K) on every turn, whether relevant or not.
  • Quota cliffs. Kimi is a flat, window-limited subscription (~60 messages per 5h, ~300/week measured). When the window is spent, Adolf goes dark. There is no graceful degradation and no way to park work until the window resets.
  • Background work is hardcoded to a model. Hindsight's reflect/consolidate call a fixed LLM directly. There is no scheduling, no priority, no quota awareness, no way to say "do this on the big model when it's free".
  • No room to grow. The ambition is autonomous research agents, remote llama nodes, more GPUs, more agents. None of that fits a single hardcoded assistant.

2. Core concepts (and the distinctions that matter)

The central insight: an agent is not a queue, and a model is not an agent.

Task

The unit of work. Durable, addressable, and context-by-reference: a task carries pointers (memory bank id, git ref, board task id, file path), never pasted context. Fields: id, intent, required capability/tier, target model queue, priority, status, context refs, result ref, submitter, deadline.

Model (backbone) — the scarce resource

A concrete LLM endpoint reached through the LiteLLM gateway. Examples today: kimi (flat quota), claude-haiku (paid, already wired), local ollama (qwen3.5:4b, qwen3:8b, gemma3:4b), later a remote llama box or a second GPU.

Each model has its own queue and its own worker, because the model is what is actually scarce (quota, VRAM, cost, rate limit).

Agent — the persona

An agent is a combination of personality + system prompt + memory + tool scope (e.g. Adolf the proactive auditor; Torgash the marketplace analyst; a research agent; the Claude coding loop). An agent is a configuration, not a runtime resource. Critically:

An agent may change its backbone LLM. Adolf on Kimi today, on a local model tomorrow, on Claude for a hard task. Therefore queues are keyed by model, not by agent. An agent submits into and consumes from model queues.

Queue — per model, async, with a lifecycle

Queues are asynchronous by design and differ in how they drain:

Lifecycle Behaviour Example
always-on worker drains continuously in the background local ollama models
quota-gated drains until the window is exhausted, then parks and resumes on reset Kimi
cost-gated drains under a budget ceiling; stops/falls back when spent paid Haiku/Flash
on-demand node is woken/attached when work exists future remote llama / extra GPU

A task parked on a quota-gated queue is not lost — it waits for the window, or is re-routed if it is urgent and another queue can satisfy the required capability.

3. Architecture

Four planes. Keeping them separate is the whole point.

┌─ Coordination plane ──────────────────────────────────────────┐
│  Task registry + lifecycle (Kanboard as blackboard today)      │
│  context-by-reference; claim/status; audit trail               │
└───────────────────────────────────────────────────────────────┘
┌─ Agent plane ─────────────────────────────────────────────────┐
│  Agent registry: persona + system prompt + memory bank +       │
│  tool scope + preferred capability tier                        │
│  (Adolf, Torgash, research-agent, claude-coder, …)             │
└───────────────────────────────────────────────────────────────┘
┌─ Scheduling plane ────────────────────────────────────────────┐
│  Per-MODEL queues + workers; lifecycle policy (always-on /     │
│  quota-gated / cost-gated / on-demand); priority; claiming     │
└───────────────────────────────────────────────────────────────┘
┌─ Model plane ─────────────────────────────────────────────────┐
│  LiteLLM gateway: kimi | claude-haiku | local ollama | remote  │
│  routing, fallback on 429/quota, per-agent virtual keys+budget │
└───────────────────────────────────────────────────────────────┘

Shared context stores (what task references point at): Hindsight (memory banks), git/gitea (code + docs), Kanboard (task context), files.

A2A on top

A2A gives the vocabulary we otherwise have to invent: agent cards (capability advertisement), task lifecycle states, structured task submission/tracking, and — most importantly — the context-by-reference pattern (send a contextId, let the worker read the shared store). We adopt the patterns first; the wire protocol can follow once more than one runtime needs to interoperate.

4. Worked examples (the required minimal set)

(1) Hindsight reflect becomes an A2A task. Reflect is async by design. Instead of Hindsight calling a fixed LLM inline, it submits a task — intent reflect, context ref = bank + query, required tier = large — onto the large-model queue. A worker runs it when that model has capacity/quota; the result is written back to the bank. Same for consolidation. This removes the hardcoded background call and makes memory work schedulable, priced, and quota-aware. (See also the "in-loop extraction" option, which is the cheaper counterpart for the retain path.)

(2) The Claude Code CLI loop is just an agent. claude-coder = an agent whose persona is "implementer", whose backbone is a Claude model, and whose consumption rule is pull complex/coding tasks. It is a special case of a queue consumer, not a privileged component. This is why it already works: Adolf files tasks, the Claude loop pulls them. We are formalising what exists.

(3) Model queues ≠ agent queues. Adolf may run on Kimi now and something else later; Torgash may be cheap-tier normally and escalate to a large model for a tricky comparison. So a task is queued against the capability/model it needs, and the agent identity travels with the task (persona + memory refs), not with the queue.

(4) Queues drain differently. The local queue works all night; the Kimi queue stops at 100% of the 5h window and resumes after reset; a paid queue stops at its budget. Submitters therefore must state urgency, and the router must be able to re-route or park.

5. Growing the lab

  • More GPUs / remote llama → new model entries + their own queues and workers; on-demand lifecycle for nodes that are not always up. Nothing else changes.
  • More agents (research, finance, home) → new agent registry entries with scoped tools + their own memory banks. They inherit queues and A2A for free.
  • Autonomous research agents → long-running, low-priority tasks on always-on local queues, escalating to the large model only for synthesis. This is exactly what per-model queues + priorities make affordable.

6. Migration (phased, smallest useful step first)

  1. Registries + schemas — model registry (endpoint, capability, lifecycle, quota), agent registry (persona/prompt/memory/tools), task schema.
  2. One queue + one worker — always-on local model, end-to-end.
  3. Quota-aware worker — Kimi: park on exhaustion, resume on reset.
  4. A2A submission/tracking with context-by-reference.
  5. Cut over the examples — Hindsight reflect → queue; Claude loop → declared agent/consumer; Adolf → declared agent with scoped tools.
  6. Scale — remote/extra models, more agents.

7. Open questions

  • Is Kanboard the queue itself, or does it stay the human-facing board while workers use a dedicated queue store (and the two are synced)?
  • Where does the routing decision live — submitter picks the tier, or a central policy re-routes based on live quota/budget?
  • How much A2A do we actually implement (patterns only vs the real protocol)?
  • Claim/lease semantics: what happens to a task whose worker dies mid-run?
  • Does an agent's memory bank follow it across backbones (yes, by design) — and what does that mean for extraction quality when the backbone is weak?
  • Kanboard epic: architecture + LiteLLM gateway + multi-agent framework.
  • Hindsight in-loop extraction (the cheap counterpart to queued reflect).
  • Per-agent tool scoping (why Adolf stops carrying every tool).