From 2499218c18a673a3d3e37f295a81225cf5f42096 Mon Sep 17 00:00:00 2001 From: alvis Date: Tue, 21 Jul 2026 06:03:29 +0000 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_014t8Qg9gi7H7HtT8MncoXAB --- DESIGN-a2a-agents.md | 170 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 DESIGN-a2a-agents.md diff --git a/DESIGN-a2a-agents.md b/DESIGN-a2a-agents.md new file mode 100644 index 00000000..a5c83ce0 --- /dev/null +++ b/DESIGN-a2a-agents.md @@ -0,0 +1,170 @@ +# 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? + +## 8. Related + +- 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).