Context
Operator feedback, live during the #17271 L1 paired session (2026-08-17): "lane claim: maybe an actor badge (who claimed something?), a2a badge => maybe sender and receiver badges, or one badge for both." Current rows are inconsistent: PR rows render an actor line ("neo-opus-grace · pr-activity"), while LANE/A2A rows render subject text only — WHO claimed or sent is invisible at a glance.
The Problem (the data is present; the render drops it)
ai/services/fleet/fleetA2AActivityAdapter.mjs (~L161-170) already maps agentId: message.from, plus from and recipientClass into the cockpit DTO — its own doc reads "the operator surface gets sender, recipient class". apps/agentos/view/fleet/ActivityStream.mjs coalesces rows BY agentId (same-actor runs), so actor identity is load-bearing in the data model — and then never rendered. The operator cannot see who did what without opening the underlying artifact.
The Architectural Reality
- Row anatomy today: time + kind chip (
EventChip owns kind rendering entirely) + subject text.
- The coalescing contract (
ActivityStream.mjs run rows: {agentId, count, events, newest}) already treats the actor as the primary key.
- The roster join supplies
avatarUrl per agentId (FleetAgent rows) — avatar-first badges are cheap.
- #15536 (AgentCard responsive anatomy) established the avatar-invariant precedent: the avatar is the fast-recognition anchor and must survive density modes.
- #17264 (unify the cockpit status-chip family) is the governing chip-system ticket — this row badge must be a SPECIMEN of that family, not a bespoke one-off.
The Fix
- ONE ActorChip per row: avatar-first (roster join), handle-text fallback; anonymous/unknown actor renders honest absence (no fabricated identity), per the stream's existing null-agentId contract.
- A2A rows additionally render the recipient half as a compact pair:
@from → @to for directed sends, a distinct broadcast glyph for AGENT:* (recipientClass is already in the DTO — broadcast vs direct is an honest, cheap distinction).
- Coalesced runs render the actor once beside the count — the coalescing key becomes visible instead of implied.
- Chip mechanics live under the #17264 family (tokens, density ladder). If #17264 lands first, consume it; if this lands first, build the chip to its spec draft and note the reconciliation.
Acceptance Criteria
Out of Scope
Pane-level mailbox rendering (#17268) · new event kinds · actor filtering/grouping UI · timestamp formatting (sibling ticket).
Related
Epic #14560 (parent) · #17264 (chip family — governing system) · #15037 (density freeze) · #15536 (avatar-invariant precedent) · #17268 (panes) · #17271 (origin session)
Live latest-open sweep: latest 20 open checked 2026-08-17T16:19Z, no equivalent; A2A claim window (last 60 min) clean; KB semantic sweep clean (hits were parent/adjacent: #14560, #14592, #15536).
Origin Session ID: 7ee47ccf-d1c7-469d-a75e-15cebf3b5ea5
Retrieval Hint: query_raw_memories("activity stream actor badge sender recipient chip family specimen")
Context
Operator feedback, live during the #17271 L1 paired session (2026-08-17): "lane claim: maybe an actor badge (who claimed something?), a2a badge => maybe sender and receiver badges, or one badge for both." Current rows are inconsistent: PR rows render an actor line ("neo-opus-grace · pr-activity"), while LANE/A2A rows render subject text only — WHO claimed or sent is invisible at a glance.
The Problem (the data is present; the render drops it)
ai/services/fleet/fleetA2AActivityAdapter.mjs(~L161-170) already mapsagentId: message.from, plusfromandrecipientClassinto the cockpit DTO — its own doc reads "the operator surface gets sender, recipient class".apps/agentos/view/fleet/ActivityStream.mjscoalesces rows BYagentId(same-actor runs), so actor identity is load-bearing in the data model — and then never rendered. The operator cannot see who did what without opening the underlying artifact.The Architectural Reality
EventChipowns kind rendering entirely) + subject text.ActivityStream.mjsrun rows:{agentId, count, events, newest}) already treats the actor as the primary key.avatarUrlper agentId (FleetAgent rows) — avatar-first badges are cheap.The Fix
@from → @tofor directed sends, a distinct broadcast glyph forAGENT:*(recipientClassis already in the DTO — broadcast vs direct is an honest, cheap distinction).Acceptance Criteria
AGENT:*) visually distinct from directed sends.recipientClasspre-existed, the directed recipient id did NOT — the adapter addsto, deliberately relaxing its documented class-only bound. Privacy holds because the caller owns the read:createA2AMessageActivityEventshas no production caller outside the module — the sole path isreadFleetA2AActivitySnapshot, which maps caller-suppliedlistMessages()output and never fetches, so the DTO cannot disclose a message the caller had not already read. Any PR/lane actor-field gap closed adapter-side with the same honest-absence contract. (Edited 2026-08-18: the original AC read "DTO untouched for A2A (fields exist)" — falsified by the shipped diff, which the AC exists to pin; corrected per PR #17351 review-4962256855 RA-2.)Out of Scope
Pane-level mailbox rendering (#17268) · new event kinds · actor filtering/grouping UI · timestamp formatting (sibling ticket).
Related
Epic #14560 (parent) · #17264 (chip family — governing system) · #15037 (density freeze) · #15536 (avatar-invariant precedent) · #17268 (panes) · #17271 (origin session)
Live latest-open sweep: latest 20 open checked 2026-08-17T16:19Z, no equivalent; A2A claim window (last 60 min) clean; KB semantic sweep clean (hits were parent/adjacent: #14560, #14592, #15536).
Origin Session ID: 7ee47ccf-d1c7-469d-a75e-15cebf3b5ea5
Retrieval Hint:
query_raw_memories("activity stream actor badge sender recipient chip family specimen")