A2A agent reporting system — agents report to Min (authority, 2026-10-20)
A2A agent reporting system — agents report to Min (authority, 2026-10-20)
Authority doc (specs only — no execution). Consolidates the design for the agent→Min reporting system built on the fleet's existing A2A transport. Nothing here executes, installs, or modifies a service. It specifies the contract, the protocols, and the integration points so that any agent (or lane) can implement against one document.
Young's ruling (inherited): one voice to Young — Min. Agents report to Min;
Min consolidates and reports to Young. This doc makes that rule mechanical:
every agent's report is an A2A message that becomes a row Min can read, verify,
and project onto the dashboard. See fleet-ground-rules-2026-10-20.md §2 Rule 2.
0. Scope and non-scope
In scope (spec only):
- A2A agent reporting system design — heartbeat + task completion + blocker reporting.
- How Min polls agents via their heartbeat (
tasks/get,tasks/send, health probes). - Work-queue integration — A2A messages become
tasks.dbrows with owner/deadline/state. - Agent ACK-then-own protocol.
- Heartbeat interval + staleness detection (300 s stuck rule).
- Integration with the fleet dashboard (
status.json). - Cross-references to the three named authority docs.
Out of scope (named, not designed here): the dialect standardisation across
planes (Heart/Pulse/Brain) — that is Sol's lane and is tracked on the fleet board
(a2a-connectivity-verification-2026-10-20.md §3). The A2A auto-session
provenance rule is named here but its implementation is Min's lane. No code,
no service changes.
1. A2A agent reporting system design
1.1 Planes and endpoints
Three planes, each a set of A2A agents with a canonical endpoint. All reachability
is via the loopback tunnels in fleet-ssh-tunnels.md (Tailscale decommissioned;
loopback-only binds both ends).
| Plane | Agents | Ports | Dial dialect |
|---|---|---|---|
| Brain (LAB) | min, mira, sol, nova, echo | 8311–8315 | v1.0 SendMessage (Brain answers both names) |
| Heart (LAB) | ace, alpha, atlas | 8301–8303 | v1.0 SendMessage + parts:[{text}] |
| Pulse (laptop) | eva, eve, tam | 8441–8443 | v0.3 message/send + content |
Authority: fleet-ssh-tunnels.md §1 (port registry), a2a-connectivity-verification-2026-10-20.md §3 (dialect matrix). A per-peer dialect: field in the registry is the planned fix for the split; until then the working recipe is recorded in a2a-connectivity-verification-2026-10-20.md §3.
1.2 Three report channels (all A2A)
Every agent reports to Min through exactly one of three A2A message shapes. No chat, no out-of-band channel, no direct-to-Young (ground rules Rule 2).
| Channel | Trigger | A2A shape | Payload |
|---|---|---|---|
| Heartbeat | periodic (§5) | tasks/send (Brain) / message/send (Pulse) | {"type":"heartbeat","agent":<id>,"contextId":<id>,"last_activity_at":<iso>,"skills_count":N,"in_flight":[<task_id>...]} |
| Task completion | a task reaches a terminal state | tasks/send + task id | the A2A Task object (status.state, artifacts, history) — Min polls tasks/get for the final state |
| Blocker report | agent cannot proceed | tasks/send | {"type":"blocker","task_id":<id>,"reason":<text>,"escalate":true} |
Task states (wire values, spec-correct, from cortex/tasks/states.yaml):TASK_STATE_SUBMITTED · WORKING · INPUT_REQUIRED · COMPLETED · FAILED · CANCELED · REJECTED · AUTH_REQUIRED. A report that names a state must name one of these; local states never reach the wire (D2 ruling in should-the-board-live-in-cortex.md).
1.3 Agent Card (discovery)
Each agent serves GET /.well-known/agent-card.json (canonical v1.0 path) with
name, protocolVersion, skills[], capabilities. Min's discovery pass reads
the card to confirm the agent is alive and non-empty (skills_count from the
card; emptiness is LOUD per dashboard §5.2 C2). A card with skills: [] is RED,
never silently absent (ground rules Rule 7 — proof before claim).
1.4 Audit trail
Every A2A exchange is appended to ~/.hermes/a2a_audit.jsonl (append-only,
per-identity). Min's polling reads the tail of this file per agent to compute
last_exchange_s (dashboard colour rules: <30 min green, <6 h amber, >6 h red).
Full audit events are out-of-band gallery only — never rendered raw on the
dashboard (Sol's annex §2 Q3).
2. How Min polls agents via their heartbeat
Min is the single poller. She does not wait for agents to push; she polls on a cadence and treats a missing/late heartbeat as a signal.
2.1 Health probe (liveness)
For each agent in the registry:
HTTP GET <endpoint>/.well-known/agent-card.json (or the A2A root for v0.3)
Any HTTP response (200/405/JSON-RPC error) = up. Connection refused/timeout = down.
Cadence: 2 min (dashboard §5.2 C2). Source of the port list:
fleet-ssh-tunnels.md registry (laptop 18301–18313, LAB→laptop 18441–18443).
2.2 Heartbeat poll (tasks/send / message/send)
Min sends a heartbeat-request message to each agent:
POST <endpoint>/ (JSON-RPC)
{"jsonrpc":"2.0","id":<n>,"method":"SendMessage"|"message/send",
"params":{"message":{"role":"user","parts":[{"text":"heartbeat"}],
"contextId":<min-context>}}}
Method name per the peer's dialect: field (§1.1). Min expects a reply within
the 300 s stuck window (§5). The reply carries last_activity_at,
skills_count, and in_flight task ids.
2.3 Task state poll (tasks/get)
For each task id Min knows about (from the audit log and the work queue), Min polls:
POST <endpoint>/ {"method":"tasks/get","params":{"id":<task_id>}}
Returns the Task with status.state. Min records the state into the work queue
(§3) and computes age vs 300 s → stuck (dashboard §5.1 live[]).
2.4 Cadence summary
| Probe | Cadence | Source |
|---|---|---|
| Endpoint liveness | 2 min | fleet-ssh-tunnels.md port registry |
| Heartbeat request | 2 min (aligned with liveness) | agent card + audit log |
tasks/get for in-flight tasks | 2 min | a2a_audit.jsonl recent ids |
| Board projection (kanban) | 5 min | Docs/Kanban/fleet-board.md + Pulse/Kanban/PULSE-BOARD.kanban |
| Tunnel probes | 2 min | fleet-ssh-tunnels.md §5 one-liners |
| System host probes | 5 min | prod free/df/systemctl |
All collectors run from the laptop (hub of the hub-and-spoke); they reach LAB via
the mesh and prod via tunnels. Collectors are read-only; they write status.json
atomically (tmp+mv). See fleet-dashboard-plan.md §5.4.
2.5 Min's polling stance (no push dependency)
Agents are not required to push. A missing heartbeat is not "no data" — it is a signal: the agent is down, stuck, or unreachable, and Min records it as such (emptiness-is-loud). This matches the ground rules' "report, don't re-plan" and Stage 8's premise that absence announces itself.
3. Work-queue integration — A2A messages become tasks.db rows
3.1 The board (REVISED 2026-10-20 22:5x — the topology changed under this section)
CORRECTION — "one board" is now "one board per workstream". The original text said
Docs/Kanban/fleet-board.md is the lifecycle truth. That is no longer true, and the
change is Young's ruling, applied by Tam (board-split-2026-10-20.md, commit 281e283).
Measured live 2026-10-20 22:5x:
| Host | Boards (the kernel, hermes kanban boards list) |
|---|---|
| LAB | docker-vhost · research · daily-operation · solos-v3 (the build board) · default (empty) |
| Pulse | daily-report-brief · research · podcast · pulse (archive) · default (archive) |
Task truth is hermes kanban, not a file. The .md board was retired as a register
(fleet-board.md now carries status: superseded); the per-board vault files
(Docs/Kanban/<board>.kanban, Pulse/Kanban/<board>.kanban) are read-only projections
generated every 30 min by no-agent crons. SOLOS-V3.md was deleted per Young (5b48edc)
and its generator now fails closed (kanban-projection-decision-2026-10-20.md).
A worker still never owns lifecycle truth; it executes and reports back. The per-agent
*-tasks.db files are attempt notebooks, not registries (Min's ruling in
should-the-board-live-in-cortex.md).
⚠️ The board split is a HOST boundary as well as a topic boundary — a card whose
assignee is not a Hermes profile on this host (e.g. tam, a Pulse profile) can never be
claimed here, and kanban.dispatch_profiles (fail-closed allowlist, LAB:
echo,min,mira,nova,sol) is what enforces that.
3.2 Row shape
Every instruction Min receives (from Young or from an agent's report) becomes one
row in the shared store operation/state/tasks.db (one file per workspace, not
per agent), with these fields (Tam addendum §2.1):
| Field | Meaning |
|---|---|
topic | what the task is |
owner | the single declared owner (ground rules Rule 1) |
deadline | ETA from the ACK (§4) |
state | board state: triage / todo / ready / running / blocked / review / done / archived |
external_task_id | the A2A task id, so one card = one task (join key) |
last_heartbeat_at | liveness of the worker |
Board states come from cortex/tasks (9 wire / 14 local states); every state that
leaves the system is a spec wire value (TASK_STATE_*).
3.3 A2A message → row mapping
| A2A message | Work-queue action |
|---|---|
| Heartbeat (§1.2) | update last_heartbeat_at for that agent's in-flight rows; no new row |
Task completion (tasks/get → terminal state) | move the row to done (or review); record the artifact ref |
| Blocker report | move the row to blocked; record reason; trigger Min's escalation path |
| New instruction from Young/Min | create a row with topic, owner, deadline, state=ready |
| Agent ACK (§4) | set owner + deadline on the row; state → running when work starts |
3.4 Min's cron
REVISED 2026-10-20 22:5x. The original text said "Every 30 min Min re-projects the
board into the vault (Docs/Kanban/fleet-board.md)". Two things are now true and neither
matches that sentence:
- The target is per-board, and it is a projection, not the register. The crons write
Docs/Kanban/<board>.kanban(LAB:docker-vhost,research,daily-operation) andPulse/Kanban/<board>.kanban(Pulse:daily-report-brief,research,podcast).fleet-board.mdis superseded and hand-edited by other agents — it must never be a generator target (a retarget would delete its custom sections; seekanban-projection-decision-2026-10-20.md). - The projection is generated, the register was hand-written. The per-board
.kanbanfiles are machine-written every 30 min by no-agent crons;fleet-board.mdwas retired as a register.
The principle is unchanged: instructions arrive once, the board carries the state, and agents derive their next step from the board rather than from re-reading chat history (Tam addendum §2.1).
3.5 The 300 s stuck rule (primary)
A task is stuck when it has produced no reply for 300 s (Young's corrected
ruling, young-rulings-2026-10-10-four.md §Ruling 3). Source:
plugins/platforms/a2a/adapter.py:_MIN_ORPHAN_TIMEOUT = 300. The watchdog sweeps
every 60 s (_WATCHDOG_INTERVAL). On timeout the task transitions to
TASK_STATE_FAILED with the message "[task orphaned — no reply produced]" —
a state, not an absence, and the state names the cause (E14, the honest scope
note).
A second, coarser condition (3600 s) records "the worker died" — kanban's
DEFAULT_CLAIM_HEARTBEAT_MAX_STALE_SECONDS. Both are recorded; only the 300 s
condition fires the follow-up loop.
✅ SCOPE — this rule governs the V3/A2A lane, NOT the Hermes board (2026-10-20 22:5x)
The 300 s rule and the Hermes board's own thresholds are two different mechanisms, and conflating them produces false positives on healthy workers. Measured:
| Mechanism | Threshold | Governs |
|---|---|---|
300 s stuck rule (cortex/tasks/stuck.py → states.yaml:stuck_rule.no_reply_seconds: 300) | 300 s | V3/A2A tasks — a task in the V3 store with no reply |
Hermes claim TTL (kanban_db.py:279 DEFAULT_CLAIM_TTL_SECONDS) | 15 min | board cards — a claim's lifetime |
Hermes stranded threshold (kanban_diagnostics.py:688) | 30 min | board cards — assigned, unclaimed, in ready |
Hermes dispatch stale timeout (config_defaults.py:1947) | 4 h | board cards — a running card with no heartbeat |
The V3 rule is correctly implemented — cortex/tasks/stuck.py reads the declared
constant from cortex/tasks/states.yaml (no_reply_seconds: 300, with
source: plugins/platforms/a2a/adapter.py:38 and ruled_by: young) and never spells a
duration in code. Measured live: kanban-t_8d7bde82 went queued -> stuck with the
cause "no reply for 423s (>= 300s) — the task is not progressing", and the escalation
was written to operation/state/escalations.jsonl.
⚠️ So: do not build a parallel 300 s detector over Hermes board cards. A 300 s rule
over a kernel whose own floor is 15 minutes would fire on every healthy worker — every
long model call, every arm64 pull. The board already emits reclaimed, crashed,
timed_out, stale and skipped_nonspawnable events; map to those, do not re-detect.
(The 300 s number remains correct for the V3 lane, where it is implemented and measured.)
A measured instance of exactly this failure, 2026-10-20 22:31–22:54: the V3 lane
projected t_8d7bde82 to running on the board with no claim_lock, and the
Hermes reconciler (hermes_cli/kanban_db_dispatch.py:845, reconcile_orphaned_running)
requeued it in the same second as orphaned_running — then the V3 lane dispatched it
again. Five cycles, and the card never ran. The card was not slow and the lane was not
dead: the two dispatchers were fighting over one card's bookkeeping.
3.6 Refinement rule
"Refine it when we have more data" is a rule, not a deferral: the 300 s value is
a declared constant with its source cited, never a magic number. Signal table
(from young-rulings-2026-10-10-four.md):
| Signal | Action |
|---|---|
tasks hitting stuck at 300 s that later complete | too short — raise it |
| tasks that stop and are not noticed for much longer | too long — lower it |
| a task class with a different natural duration | a per-class override, not a global change |
4. Agent ACK-then-own protocol
The working pairs' discipline, made the required default (Tam addendum §2.2):
Whoever receives a task replies within minutes with either "ACK, owning, ETA" or "ACK, escalating to X" or "ACK, blocked on Y".
The absence of an ACK is what Young reads as "losing grip" — because he cannot tell who took the task. This is a one-line rule in every agent's memory, not a skill rewrite.
4.1 The three legal ACK shapes
| ACK | Meaning | Work-queue effect |
|---|---|---|
ACK, owning, ETA <t> | I took it, I will deliver by | set owner + deadline; state → running when work starts |
ACK, escalating to X | I am not the owner; handing to X | re-assign owner=X; X must ACK in turn |
ACK, blocked on Y | I cannot proceed; blocker Y | state → blocked; reason recorded; Min escalates |
4.2 Multi-tag rule (group channel)
Any message tagged to 3+ agents → the first to ACK owns the topic; the rest explicitly ACK "yielded to owner" or stay silent unless @-mentioned for a specific question. Verification questions (Young asking Tam to verify a specific fact) are the exception — those carry a direct tag and are answered directly (Tam addendum §2.4).
4.3 Find-before-register gate
Before creating any new vault file: find Docs -iname "*<topic>*". If a related
file exists, that file is the home — write a section, don't spawn a sibling
(Tam addendum §2.3; ground rules Rule 4). This is the direct causal fix for the
"same task written multiple times" symptom.
4.4 No agent reports to Young directly
Min is the single voice. An agent's report goes to Min via A2A; Min consolidates and reports to Young. An agent speaks to Young only when directly @-mentioned by Young or Min (ground rules Rule 2).
5. Heartbeat interval + staleness detection
5.1 Interval
- Heartbeat request cadence: 2 min (aligned with the liveness probe, §2.1).
- Board projection: 5 min (kanban).
- System host probes: 5 min.
- Tunnel probes: 2 min.
- Collector cron (vault re-projection): 30 min (Tam addendum §2.1).
5.2 Staleness bands (agent health)
From fleet-dashboard-plan.md §5.3 and Sol's annex:
| Signal | Green | Amber | Red |
|---|---|---|---|
| Agent endpoint | responds | — | refused/timeout |
| Last exchange | <30 min | <6 h | >6 h |
| In-flight task | <300 s | — | ≥300 s (stuck) |
| Skills count | non-empty | — | empty (LOUD) |
5.3 Stuck rule (primary)
300 s without progress → stuck → Min's follow-up fires without a human deciding
(§3.5). A task that stops is a state, not an absence, and the state names the
cause (E14).
5.4 A2A auto-session provenance
Inbound A2A spawns a per-context profile session (151 such sessions in Echo's
log) that answers as the agent outside its interactive session. It shares memory
but not conversation state. An A2A reply is that agent's answer but not
necessarily a deliberate one from its interactive session. Provenance must be
logged with the session id in the audit log (a2a-connectivity-verification-2026-10-20.md §4). Named here; implementation is Min's lane (fleet board card).
6. Integration with the fleet dashboard (status.json)
The dashboard is the human face of this reporting system. It consumes the same data Min collects; it does not build a second collector stack (Sol's annex §1).
6.1 status.json v0 schema (from fleet-dashboard-plan.md §5.1)
{
"generated_at": "<iso8601>",
"stale": false,
"tasks": { "board": { "columns": {...}, "cards": [...] },
"live": [ {"id","agent","age_s","stuck":bool,"state"} ] },
"agents": [ {"name","endpoint","up":bool,"last_exchange_s","skills_count",
"lane","model","window":{"model","percent","status","resets_at"} } ],
"systems": [ {"host","ram_used_gb","ram_total_gb","disk_free_gb",
"services":[{"name","active"}],"tunnels":[{"name","up"}],
"crons":[{"name","last_ok_s"}],"vault_parity":bool} ],
"backups": { "gdrive_latest": "<date>", "age_h": N }
}
Rules: every array element carries its own checked_at; a failed probe fills the
field with "error: <reason>" and sets that element's stale=true — the panel
renders red, never omits (emptiness-is-loud).
6.2 Agents panel (Min's read of the reporting system)
One row per agent (11): endpoint status (probed), last successful exchange (from
a2a_audit.jsonl), skills count (empty = loud), lane, model, GO window %.
Colour rules per §5.2. The reporting system's heartbeat data feeds
last_exchange_s and in_flight (→ live[]).
6.3 Tasks panel
Kanban board projection (columns/cards/owners from the .kanban file, auto-synced
via git) plus "Live tasks" — in-flight A2A tasks with age vs the 300 s rule,
shown separately. A task ≥300 s shows a red STUCK badge (dashboard-page-design.md).
6.4 Systems panel
Hosts (laptop, LAB, prod): RAM/disk/services/tunnels-up/crons-last-run/vault-parity; backup freshness. Collectors run from the laptop (hub-and-spoke); read-only.
6.5 Serving
status.json emitted by Stage 8's collectors on the loopback at /api/status.json,
served by prod nginx behind auth_basic (single user young). Refresh: collectors
run on cron (2–5 min); dashboard renders the last snapshot — no heavy live polling
from the browser. Failure rule: collector cron is deliver=local, quiet-failure;
the dashboard's own staleness banner is the alert.
6.6 What must NOT be on the dashboard
Per Sol's annex §2 Q3: no tokens/keys, no per-tenant .env, no Telegram tx hashes,
no provider keys, no JWTs, and never a2a_audit.jsonl raw events (only
last-exchange age). Only slices cross the gate.
7. Cross-references
7.1 fleet-ground-rules-2026-10-20.md
- Rule 1 (one owner per topic): every topic has one owner; the owner writes the file. Applied to reporting: each agent owns its own lane's reports; Min owns the consolidated report to Young.
- Rule 2 (one voice to Young): only Min reports to Young. This entire reporting system exists to make that rule mechanical — agents report to Min via A2A, Min consolidates.
- Rule 4 (one file per topic): the work queue is one board, not five kanban files.
- Rule 5 (report, don't re-plan): agents execute assigned work and report back; they do not decide what to do next — Min decides. The ACK-then-own protocol (§4) is the operational form of this rule.
- Tam's addendum (Part 2): ACK-then-own, find-before-register, work-queue + one Kanban file — all adopted here as §3 and §4.
- Sol's Rule 8 (blockers reported, not absorbed): blocker reports (§1.2) are the mechanism.
7.2 fleet-ssh-tunnels.md
- Port registry: all A2A endpoints are reached via the loopback tunnels (laptop 18301–18313 → LAB 8301–8313; LAB → laptop 18441–18443). No Tailscale.
- Health probes (§2.1) use the ports in this registry; the one-liners in §5 of that doc are the tunnel probes Min runs.
- Standing rules: loopback-only binds both ends; register every new tunnel in that note; reboot survival pattern (user units + linger + autossh).
- Dialect routing (
a2a-mesh-rectify-2026-10-20.md): Heart/Pulse/Brain speak different dialects; a per-peerdialect:field in the registry is the planned fix. Until then the working recipe (Heart → v1.0SendMessage+parts; Pulse → v0.3message/send+content; Brain → either) is the contract.
7.3 fleet-dashboard-plan.md (and Sol's annex)
- status.json v0 schema (§6.1) is the dashboard's data contract; the reporting system's heartbeat/task/blocker data feeds it.
- Collectors (§5.2 of that doc: C1 board, C2 agents health, C3 systems health,
C4 backups) are the implementation of §2's polling; read-only, run from the
laptop, write
status.jsonatomically. - Colour rules (§5.3) are the staleness bands of §5.2 here.
- Sol's annex:
observe.pyis the data contract (do not rebuild); the dashboard isstatus.jsonemitted by Stage 8 collectors (no second stack);a2a_audit.jsonlis projected, never rendered raw; emptiness is loud. - P0/P1/P2 phasing: data contract → first page → refresh + alerts (stuck-task alarm at 300 s is P2).
8. What this doc does NOT do
- It writes no code and modifies no service. Specs only.
- It does not standardise the A2A dialects across planes (Sol's lane; tracked on the fleet board).
- It does not implement the A2A auto-session provenance rule (Min's lane; named in §5.4).
- It does not build the collectors or the dashboard (Tam/Sol lanes; specified in
fleet-dashboard-plan.md). - It does not change any agent persona or config.
9. Open items (registered, not resolved here)
| # | Item | Owner | Source |
|---|---|---|---|
| 1 | Mira + Sol have zero a2a_agents peers configured — cannot send A2A | Min (registry/projection) | a2a-connectivity-verification-2026-10-20.md §2 |
| 2 | A2A dialects diverge per plane; record per-peer dialect: in the registry | Sol | a2a-connectivity-verification-2026-10-20.md §3 |
| 3 | A2A auto-session provenance must be logged with session id | Min | a2a-connectivity-verification-2026-10-20.md §4 |
| 4 | Work-queue dispatcher: one tasks.db + single Kanban board projection cron | Min | fleet board card |
| 5 | Standardise on one A2A dialect (upgrade Pulse to v1.0 or add v0.3 compat) | Sol | a2a-connectivity-audit-2026-10-20.md §5 |
| 6 | Clean up stale Heart listeners (8304/8305) | Sol | a2a-connectivity-audit-2026-10-20.md §5 |
Rule: specs only. This document records the design. Implementation is tracked
on Docs/Kanban/fleet-board.md and owned by the lanes named above.

