From 4e67186c080acd8d80e4609632103096628d5b71 Mon Sep 17 00:00:00 2001 From: alvis Date: Wed, 22 Jul 2026 11:06:34 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20A2A-3=20=E2=80=94=20task=20schema,=20fi?= =?UTF-8?q?eld-by-field=20KB-literal=20mapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds §6a documenting how each Task field (id, intent, required tier, target queue, priority, status, context refs, result ref, submitter, deadline) is actually represented in Kanboard today (columns, tags, score, priority, comments, native fields), per the already-agreed KB-literal decision (§10.2). Ground truth taken from kb-claim and kb_worker.py, not written in the abstract. Kanboard #135 --- DESIGN-a2a-agents.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/DESIGN-a2a-agents.md b/DESIGN-a2a-agents.md index 2e4729c4..b4c99662 100644 --- a/DESIGN-a2a-agents.md +++ b/DESIGN-a2a-agents.md @@ -323,6 +323,32 @@ 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 ` / `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