# 001 - Agent Orchestrator ## User Story As the operator of many parallel agents, I want one simple Agent Orchestrator surface and a radio that only interrupts for urgent calls, so that agents spend few tokens on talk, urgent news reaches them at once, and I can drill from three views down to any agent, branch or artefact. ## Decisions (operator-confirmed 2026-11-06; do not re-litigate) 1. **One document**: radio model, surface, pkgmgr branch tree and views live in this one requirement as phases. Rejected: one document per feature (the operator wants the whole picture in one place). 2. **Location**: `docs/requirements/NNN-slug.md`, numbered per repository. Rejected: a file next to the code (no index, no numbering). 3. **Radio classes**: `post`, `call`, `newsletter`, `broadcast`, `mayday`, `pan-pan`, `security`. Rejected: `broadcast` only as the umbrella of the three alarms (the operator wants an immediate call to all without alarm). 4. **Mailbox**: `post` (one agent) and `newsletter` (all agents) are delivered only when the agent ends its turn and goes idle, batched; an agent already idle is not woken and sees its mailbox at the end of its next turn. Rejected: next prompt, next tool step (interrupts work), batched wake-ups (one turn per wake). 5. **Immediate classes**: `call`, `broadcast`, `pan-pan`, `mayday` and `security` reach the agent at its next tool step and wake an idle agent. 6. **Call receipt**: the caller gets a delivered/read receipt and does not wait; an answer is a call of its own. Rejected: the caller blocks until the answer (costs the caller), delivery without feedback. 7. **Mayday pause**: while a mayday holds, a PreToolUse hook blocks every writing action until `radio.py clear`; reading, the radio and teardown stay allowed. The sender and every agent that joins with `radio.py assist` to help keep working. Rejected: an instruction only (not enforced), heavy jobs only (today's behaviour, too weak), a full blockade that also stops the helpers. 8. **Radio and post**: two channels. `radio.py send` is the radio and always immediate (`call`, `broadcast`, `pan-pan`, `mayday`, `security`); `radio.py post` is mail and always delayed (`post` with `--to`, `newsletter` without). `send direct` stays as an alias so running peers keep working: with `--wake` it becomes `call`, without it `post`. Rows stored before the change carry no version mark and are read as before: `direct` as `post`, `broadcast` as `newsletter`. Rejected: `send broadcast` as an alias of `newsletter` (makes the immediate broadcast unreachable). 9. **Name**: kvb-hub is renamed in full, with a one-time migration: visible "Agent Orchestrator", technical `kvb-orchestrator` / `kvb_orchestrator` (directory, module, container, systemd unit, MCP server, environment `KVB_ORCHESTRATOR_*`, make targets). Rejected: visible name only (two names for one thing). 10. **Navigation**: Stream (sub-items Inbox and Firewall) · Agents (ex Graph) · Artifacts (ex Tree). Agents and Artifacts each offer Orbit, Chord, Swimlane, Tree and 3D. 11. **Header**: title · ⌘K · ⚙ only. The status pills become navigation badges (Inbox: waiting items, Agents: active agents, Artifacts: runner jobs); Mayday shows only as a red banner with "cancel" while active; the background shows day and night. Rejected: one status dot with a popover (an extra click), a status block in a side bar, a "Situation" screen (effort), no header at all. 12. **Tools**: Controls (Radio to all, Cancel mayday, Priority, Day/Night) moves to the Agents toolbar, Firewall under Inbox, Sky, Music, Control room and Notifications to ⚙ "Ambience"; ⌘K reaches every one. Rejected: one ⋯ menu (hides instead of ordering). 13. **Stream**: four category chips For you · Radio · Work · Transcripts; one search line with `agent:`, `kind:`, `since:`; sub-filters, saved views and a custom range under "Filter ▾"; the time bar becomes "24 h ▾" with the replay inside; transcripts are off by default. Rejected: a self-grouping stream, a Kibana-like query language (effort), preset chips only. 14. **3D**: `3d-force-graph` 1.80.1 on `three` 0.186.1, vendored as in meta.infinito.nexus; the Canvas depth axes Time and Spawn depth become 3D options. Rejected: keeping the Orbit-only Canvas depth. 15. **Artifacts scope**: git (repositories → branches → worktrees with their agents and reviews) plus the kvb-org-rasci register artefacts (images, volumes, logs) as leaves of the task or worktree that owns them. Rejected: git only. 16. **Artifacts views**: Orbit: repository sun, branch planets (size = commits ahead), worktree moons, agent satellites. Chord: branch overlap (same files touched, merges) as conflict risk. Swimlane: one lane per branch with commits, reviews, gates and pushes. Tree: the pkgmgr branch tree. 17. **Tree data**: the scan moves from `hub/kvb_hub/tree.py` into pkgmgr as `pkgmgr branch tree --format json|yaml --output FILE`, rewritten by a systemd user timer every minute and after every pkgmgr branch action; the orchestrator only reads the file, no git runs in its container. Rejected: the hub keeps scanning (pkgmgr is not the single source, git runs in the container), a live websocket daemon (effort). Hardened through kvb-think-dialectic before it is built. 18. **Order**: 1 radio → 2 surface and rename → 3 pkgmgr branch tree → 4 views and 3D; one bundle per phase, each change set through kvb-code-refactor. 19. **Icons over text**: every HTML surface a skill renders (the orchestrator, the review pages, the meditation page) shows Bootstrap Icons instead of text labels wherever an icon carries the meaning, each with a tooltip and an `aria-label`; terminal output and `SKILL.md` keep emoji. Rejected: Font Awesome Free (icons under CC BY 4.0, core loads it from a kit CDN), emoji in HTML (look differs per platform). 20. **Branch state**: `pkgmgr branch tree` covers every repository pkgmgr manages and gives each branch and worktree the classification of `pkgmgr branch gc` (one implementation, no second copy): merged and by what, clean or uncommitted changes, unpushed commits, gone upstream, locked, stale, protected and the gc skip reason. The Artifacts Tree shows it as icons. Phase 3 builds the data, phase 4 the icons. 21. **Delete from the tree**: merged, orphaned branches are deleted by click in the Artifacts Tree (phase 4), per item and as "Delete all merged": - **Credentials**: only while the Secret Service hands pkgmgr the GitHub token, which proves the merge and the missing open pull request; no secret passes the orchestrator container. Rejected: the master password typed into the dialog (secret crosses container and browser), the SSH key through the KeePassXC agent (a local delete pushes nothing; operator 2026-11-06). - **Scope**: local only (operator 2026-11-06): the worktree, the local branch, the compose stack without its volumes and its images of a `merged-clean` item, never a remote branch; a gc skip reason locks the button, and the dry run lists ignored files, any beyond known caches (`__pycache__`, `node_modules`, `.venv`) locking it too. Rejected: what `pkgmgr branch gc --apply` deletes today (remote branch and `compose down -v`). - **Confirmation**: the click shows the gc dry-run lines of the chosen items, and "Delete" needs the operator signature (`/api/sign`). Rejected: a second click without signature, one click without preview. - **Execution**: the orchestrator writes a signed job into a host directory; a systemd user path unit runs `pkgmgr branch cleanup --apply --local` for exactly those items and writes the result back. Rejected: an `operator.py` row (not direct). 22. **Organisation update**: a new skill `kvb-org-update` tells every agent and client which skills changed and how to use them, as one `newsletter` per update; phase 1 announces the radio change through it. 23. **Phase 5** (23 to 43 operator-confirmed 2026-11-07): the console output shows under "Run"; a deny picks its channel (default `post`); agents request auto-approval rules through the inbox; the rules form and the Location fields never overlap; the radio plays with one line per station and highlights the speaking agent; the operator composer sends text or voice, recorded, transcribed locally with whisper in the container and kept as audio, to a recipient by `post` or `call`; a Hierarchy view shows the council offices with appoint and recall; the agent order (drag list) is separate from task priority (WSJF plus the operator's pin) and only breaks ties; one Tasks tab holds list, board and RASCI matrix, the inbox as its "waits on Operator" filter and the queue state; the merge flow, a pkgmgr-managed YAML per worktree with several pull sources and push targets, shows in Artifacts and Tasks. 24. **Firewall**: the rules tab becomes Firewall and holds every form of allow-listing: the hub's auto-approval rules (ask, review, decision), allow and deny command patterns, and the Claude settings permissions (`allow`, `ask`, `deny`) and sandbox entries. Rejected: a separate settings page. 25. **Settings writes**: a change to a Claude settings file shows as a diff, is signed with the passkey and applied by a host executor that verifies the signature, writes only files of a fixed list (`~/.claude/settings.json` and `/.claude/settings.json` of the known projects) and keeps a backup. Rejected: an `operator.py` row only (no one-click path); an agent writing settings (never). 26. **Groups**: a rule applies to one agent, a named agent group or all agents; rules form named rule sets that are taken over as a whole for an agent, a group or all. The Firewall reads the existing settings files and groups their entries by tool and command prefix by itself. 27. **Decisions are tasks**: asks, rule requests, operator rows and choices appear only in the Tasks tab, under its "waits on Operator" filter. Rejected: a Choices column of its own (a choice is a decision the operator takes, so it is a task). 28. **Request choices**: the Tasks tab has one button per channel, `post` and `call` with a multi-select of agents, `newsletter`, `broadcast`, `security`, `mayday` and `pan-pan`, that sends the fixed request "send me your open decisions as choices" without free text. Agents answer with `kvb-think-choice` items, shown as choice cards; the operator's click on an option is the passkey-signed answer. 29. **Night commands**: evening, sunset and sunrise become `drain` (finish the current task, accept nothing new), `suspend` (park work, write the handover, idle) and `resume` (continue from the handover). Skills: `kvb-org-evening` → `kvb-org-drain`, `kvb-org-sunset` → `kvb-org-suspend`, `kvb-org-sunrise` → `kvb-org-resume`, `kvb-agent-relax` → `kvb-agent-drain`, `kvb-agent-sleep` → `kvb-agent-suspend`, `kvb-agent-wakeup` → `kvb-agent-resume`; `night.py` and the Controls menu follow, `kvb-skill-cleanup` removes the old names. Rejected: sleep / hibernate / wakeup, pause / park / resume. 30. **Agent popup**: a click on an agent opens a fading popup with a simple, clear layout (face, name, earlier names, offices, tasks, claims, composer) instead of the Details side panel, which goes. 31. **Node popup**: a click on a node in any graph view enlarges it, marks it red and opens a slightly transparent popup in the foreground, tied to the node by a leader line. A search field with suggestions replaces the list of every node. 32. **Faces**: agents get simple animated 2D faces instead of emojis, built from independent attributes (shape, colour, eyes, brows, mouth, hair or headwear, accessory) with at least 1000 combinations, chosen deterministically from the agent's stable id; the mouth moves while the agent speaks. 33. **Fit to window**: every view sizes to the window height; no page scrollbar on `#agents/orbit`. 34. **Rename once**: a name change appears once in the Stream per session and name pair, never again per state build; earlier names show in the agent popup. 35. **Radio**: the configured Deutschlandfunk stream `https://st01.sslstream.dlf.de/dlf/01/high/aac/stream.aac?aggregator=web` plays (root cause proven first, redirects and `media-src` included). A squelch control suppresses the radio beep; at 0 no beep plays, music on or off. 36. **Tree cause**: the tree timer builds pkgmgr from the newest signed tag, and v2.3.0 has no `branch tree`, so no tree file exists. The Tree names that cause and the operator step (the pkgmgr release) instead of a bare "No tree"; with pkgmgr 2.4.0 released the timer writes the file. 37. **Resources**: a tab Resources shows charts on the shared time axis: tokens per agent and accumulated (from the usage fields of the session transcripts), and CPU, RAM, disk and load of the host, sampled by a host unit into the state directory; several values per chart. 38. **Order of phase 6**: serial subtrees, each in its own worktree under `kvb-org-robot`, merged into master before the next starts: S0 English and radio speech → S1 fixes (fit, rename once, radio, squelch, tree cause) → S2 popups and search → S3 faces → S4 night commands → S5 Tasks with asks and choices → S6 Firewall → S7 Resources. 39. **Sécurité**: the class `security` keeps its id and shows as "Sécurité"; the radio speaks it as in maritime radio, "say-cure-ee-tay", three times. 40. **Hashes spoken**: a hex run of 7 or more characters with at least one letter and one digit (commit, session and message ids) is spoken in the NATO alphabet, one character at a time (alfa, bravo, charlie, delta, echo, foxtrot and the digits of decision 41). 41. **Digits spoken**: every digit is spoken on its own in ICAO radio pronunciation: zero, one, two, tree, four, fife, six, seven, eight, niner; `42305` is "four two tree zero fife". Only the spoken text changes; the displayed text keeps the digits and hashes. 42. **English**: the whole skills repository is English: every orchestrator label, message, spoken text, script output, document and test string (Regeln → Firewall, Hierarchie → Hierarchy, Ressourcen → Resources, Steuerung → Controls, Ausführen → Run). Kept: trigger phrases in skill descriptions that quote what the operator says in German, and the rule that agents answer the operator in the operator's language. 43. **Shared cache** (core concept settled by the operator 2026-11-07 18:40, owner IAM, report `/tmp/shared-model-cache-base-20261107.md`): the image build fetches every model, voice and source archive from `${INFINITO_ARTEFACT_BASE}//` with the same sha256 pins, and from the real upstream when the build arg is unset. Speech uses the core role `svc-ai-speech` when its URL is configured, else the in-container whisper and piper. `make orchestrator-install` without any core stack stays the standalone path. Subtree S8, after S7. 44. **Gantt** (operator 2026-11-08, decisions 44 to 51): the Tasks tab gets a Gantt view next to List, Board and Matrix, one bar per task from its start to its end, a running task to now. Arrows come from three sources: an explicit `rasci.py set --after ` (repeatable), the branch base (a task whose base branch is another task's branch follows it) and the queue order (each waiting `queue.py` job follows the one ahead of it). Rejected: branches only (tasks without a branch hang loose), the explicit field only, the queue position as a mere badge. 45. **Forecast**: a selector sets how the Gantt draws tasks not yet started: `always` (default; with no estimate the median duration of done tasks, with story points the points times the median hours per point of done tasks), `points only` (a dot behind the predecessors without estimate) or `real only` (no forecast). Forecast bars are hatched. 46. **Estimation modes**: a toggle in the Tasks toolbar, stored in the hub settings: `No estimate` (default, every task counts 1) and `Story points` (the Fibonacci complexity of `rasci.py estimate`, the median over its estimators). 47. **Burndown and burnup**: one view in the Tasks tab with both curves. A selector sets the scope: the whole register over the whole time (default), the active list filter within the header time window, or one agent or line. With no estimate it counts tasks, with story points it sums complexity and draws unestimated tasks as a line of their own. 48. **Estimate request**: only in story points mode, and only on a click on "Request estimates" or on `/kvb-org-queue-estimate`, never by itself, to save tokens. It sends one `newsletter` to all agents listing every unestimated task with its parties; every agent holding any RASCI role in a task (R, A, S, C or I) records business value and complexity (Fibonacci) for it with `rasci.py estimate`. Rejected: every agent estimates every task, the Accountable alone. 49. **Operator business value**: the operator overrides the agents' business value, and WSJF uses that value instead of the median. It is set in the hub per task or as one bulk change over a selection in the list; one passkey assertion over the exact task ids and values covers the whole batch. Rejected: the origin guard alone (a forged request could reorder the queue), one signature per task. 50. **Sort modes**: the task list sorts by prio (default), WSJF, business value, complexity, status, start, end, agent and title, each ascending or descending. 51. **Subtree**: decisions 44 to 50 join subtree S5, Tasks with asks and choices, in its one worktree. Rejected: a subtree of their own after S8 or right after S1. 52. **Radio spot** (operator 2026-11-09, decisions 52 to 58): the radio button of the Agents toolbar is the one place to send. It expands inline and shows the full composer below the toolbar (every channel, the recipients for post and call, Record, Send), a gear button with every radio setting (tones per class, squelch, read aloud with its classes and speakers, moved out of the general settings) and Cancel mayday, which leaves the toolbar. The Radio tab keeps the stations and the message log without a composer. Rejected: the Radio tab as the spot, removing the Radio tab. 53. **Day/Night menu**: only the status badge and the Drain, Suspend and Resume buttons; each button's explanation is its hover tooltip. 54. **Tasks toolbar**: views, filter, estimation toggle, sort and business value in one row that wraps on narrow screens; the request choices become one "Request choices" dropdown in that row; the status chips stay the second and last row. Rejected: moving the request choices into the radio spot. 55. **Trend and forecast**: burndown and burnup get a trend selector, `off` (default), `linear regression` (least squares over the points in the shown window) and `velocity` (the mean burn rate of the last N days, N set freely, default 7), stored in the hub settings. The trend is drawn until remaining reaches 0 (burndown) or done meets scope (burnup); that date is the forecast, shown on the chart. Hovering a point lists the work packages that started, ended or joined the scope at that step. 56. **ETA**: every work package in the list carries an ETA: its end when done, else its start (or now) plus the forecast length of decision 45 for the active estimation mode, behind the latest ETA of its predecessors. 57. **Tasks 3D**: a sixth Tasks view on the 3D engine of the Agents tab: tasks as nodes, the dependency edges of decision 44 in the Gantt colors, layouts Force and Time, the same tooltips and popups. Rejected: a fixed time-agent-depth layout. 58. **Steady 3D camera**: the 3D view fits the graph once per build; state refreshes keep the operator's camera. 59. **Agent states** (operator 2026-11-10, decisions 59 to 62): the hub derives six states per agent session: sleeping (drain or suspend part, idle), suspending (drain or suspend part, still busy), working (process alive, busy), alive (process alive, idle), dead (process gone while a claim or task still names it) and unreachable (busy without an update for 15 minutes, or a call without a receipt for 10 minutes). Inputs: the Claude Code session files (status, pid and its start tick), the night state and the radio receipts; a runtime without a session file shows alive or dead by its claim heartbeat. The hub's container sees neither the session files nor the host's processes, so a host timer (`kvb-orchestrator-sessions.timer`, every 15 seconds while the page polls) writes a snapshot with each process checked into the hub's state; one older than 5 minutes counts as absent. Rejected: an agent hook reporting its own state (a hook and a reinstall on every agent), the claim heartbeat alone (no working versus alive), the host's PID namespace or `/proc` in the container (every process's command line in reach of the web-facing hub). 60. **State symbol**: an animated Bootstrap icon from the vendored sprite next to every agent in Orbit, Chord, Swimlane, Tree and 3D and in the agent panel; `prefers-reduced-motion` stops the animation. Rejected: emoji (font-dependent), a colored ring only (six states hard to tell apart). 61. **Status menu**: a "Status" menu in the Agents toolbar holds one checkbox per state and "Faces in 3D", stored per browser like the saved views, no passkey. Rejected: hub-wide settings with a signature. 62. **3D faces**: agent nodes in the 3D view show their face, on by default and switchable; repositories and branches stay spheres. ## Approach ```mermaid flowchart TD subgraph P["🧭 0 · Preparation"] P1["📋 Write requirement"] --> P2{"✅ Operator approves?"} P2 -- "✏️ corrections" --> P1 end P2 -- "🤖 yes" --> R1 subgraph R["📻 1 · Radio model"] R1["🔴 Red tests: classes, mailbox, receipt, mayday block"] --> R2["🛠️ radio.py, Stop mailbox hook, PreToolUse mayday hook, SKILL.md"] R2 --> R3{"🧪 Tests and make test green?"} R3 -- "🔴 no: fix" --> R2 R3 -- "🟢 yes" --> R4["🧹 kvb-code-refactor"] R4 --> RF{"💬 Operator feedback?"} RF -- "✏️ changes" --> R2 end RF -- "👍 commit word" --> S0 subgraph S["🧭 2 · Surface and rename"] S0["🔀 Rebase feature/kvb-hub onto origin/master"] --> S1["🔴 Red tests: nav, header, badges, stream, migration"] S1 --> S2["🛠️ Nav, header, badges, banner, stream, inbox, tools, rename + migration"] S2 --> S3{"🧪 Tests green?"} S3 -- "🔴 no: fix" --> S2 S3 -- "🟢 yes" --> S4["📸 Screenshots 1920 / 1366 / 390 px"] S4 --> SF{"💬 Operator feedback?"} SF -- "✏️ changes" --> S2 end SF -- "👍 commit word" --> T0 subgraph T["🌳 3 · pkgmgr branch tree"] T0["⚖️ kvb-think-dialectic on the data path"] --> T1["🔴 Red tests on fixture repositories"] T1 --> T2["🛠️ pkgmgr branch tree with gc states, timer, orchestrator reads the file"] T2 --> T3{"🧪 Tests green, no git in the container?"} T3 -- "🔴 no: fix" --> T2 T3 -- "🟢 yes" --> TF{"💬 Operator feedback?"} TF -- "✏️ changes" --> T2 end TF -- "👍 commit word" --> V1 subgraph V["🪐 4 · Agents and Artifacts views"] V1["🔴 Red tests: five views per tab, artefact leaves, delete job"] --> V2["🛠️ Orbit, Chord, Swimlane, Tree with state icons and delete, 3D (3d-force-graph), host gc unit"] V2 --> V3{"🧪 Tests green?"} V3 -- "🔴 no: fix" --> V2 V3 -- "🟢 yes" --> V4["📸 Screenshots of every view"] V4 --> VF{"💬 Operator feedback?"} VF -- "✏️ changes" --> V2 end VF -- "👍 fits" --> D1["🏁 Full gate, commit with operator OK, push hand-over"] ``` Phases 5 and 6 (decisions 23 to 42): ```mermaid flowchart TD subgraph W["🧩 5 · Inbox, UI, Hierarchy, Tasks"] W1["🛠️ W1 to W4 in own worktrees"] --> W2["🕵️ Skeptic round, fix round per worktree"] W2 --> W3{"🧪 Suite, e2e, MEDUSA, score green?"} W3 -- "🔴 no: fix" --> W2 W3 -- "🟢 yes" --> W4["🔀 Rebase on master, commit, merge one by one"] end W4 --> L1 subgraph L["👂 6.0 · Listen and record"] L1["❓ kvb-think-listen: names, Firewall writes, choices, groups"] --> L2["📋 Decisions 24 to 42 in this file on master"] end L2 --> S0 subgraph S["🤖 6.0 to 6.7 · Serial subtrees under kvb-org-robot"] S0["🇬🇧 S0 English, Sécurité, NATO hashes, ICAO digits"] --> S1["🩹 S1 fit, rename once, radio, squelch, tree cause"] S1 --> S2["🪟 S2 agent and node popups, search"] S2 --> S3["🙂 S3 animated faces"] S3 --> S4["🌙 S4 drain, suspend, resume"] S4 --> S5["🗳️ S5 Tasks: asks, choices, Gantt, estimates, burn charts"] S5 --> S6["🧱 S6 Firewall and settings writes"] S6 --> S7["📈 S7 Resources charts"] S7 --> S8["🗄️ S8 artefact base URL, optional core speech"] end subgraph G["🔁 Each subtree"] G1["🔴 Red tests"] --> G2["🛠️ Change"] G2 --> G3{"🧪 Suite, e2e, MEDUSA, score green?"} G3 -- "🔴 no: fix" --> G2 G3 -- "🟢 yes" --> G4["📸 Screenshots 1920 / 1366 / 390 px"] G4 --> GF{"💬 Operator feedback?"} GF -- "✏️ changes" --> G2 GF -- "👍 fits" --> G5["🔀 Commit, merge into master, next subtree"] end S8 --> D2["🏁 install-skills-master row, push hand-over"] ``` ## Acceptance Criteria ### Radio model - [ ] `radio.py send` accepts exactly `call`, `broadcast`, `pan-pan`, `mayday`, `security` and the alias `direct`; `post`, `newsletter` and any other class exit 2. - [ ] `radio.py post --to X` stores `post`; `radio.py post` without `--to` stores `newsletter`. - [ ] `call` and `send direct` require `--to`; `broadcast`, `pan-pan`, `mayday` and `security` exit 2 with `--to`. - [ ] `send direct --wake` is stored as `call`, `send direct` as `post`. - [ ] New rows carry a version mark; rows without it are read as before: `direct` as `post` (as `call` when it woke), `broadcast` as `newsletter`. - [ ] The UserPromptSubmit hook (`radio.py hook`) and the PostToolBatch hook (`radio.py tool`) never print a `post` or `newsletter` row. - [ ] A new Stop hook `radio.py mailbox`, registered in `scripts/enable-plugins.js`, prints the unseen `post` and `newsletter` rows of the session at most 10 at a time with a "N more" line, marks them seen and exits 2; with nothing unseen it exits 0 and prints nothing. - [ ] `radio.py listen` wakes an idle session only for `call`, `broadcast`, `pan-pan`, `mayday` and `security`, never for `post` or `newsletter`. - [ ] `radio.py tool` delivers `call` (to its addressee), `broadcast`, `pan-pan`, `mayday` and `security` at the next tool step. - [ ] When a call reaches the callee's context, a receipt row (callee, call time, read time) is stored; the caller's mailbox shows "CALL read