diff --git a/herdr/EXAMPLE.md b/herdr/EXAMPLE.md index 24e4a33..72f25c0 100644 --- a/herdr/EXAMPLE.md +++ b/herdr/EXAMPLE.md @@ -130,7 +130,8 @@ Step by step, and *which skill fires when*: 1. **web raises the ask (child `cross-coordinate`, outbound).** web opens a tracking item (`gh issue` #42 with the contract detail), then injects **one line** into the Coordinator's - pane — never straight to `api`: + pane with `herdr pane run` (the form that supplies the Enter — `send-text` would leave it + sitting unsent in the Coordinator's composer) — never straight to `api`: > `From web: need GET /users/:id to return avatar_url so the profile page can render it. Needs api. Detail in acme/web#42.` Then it **stops reaching toward api** and does what web-side work it can while it waits. @@ -180,5 +181,8 @@ identical. out of date with itself. And a reversal goes to *every* pane that got the original. - **The Coordinator's real job is timing** — protecting a busy pane from interruption and handing off only when it's safe, escalating to you when it's murky. +- **Delivery is the floor under all of it.** Send with `pane run` (text *plus* Enter) or the + message never submits, and never judge a pane's readiness from its `❯` line — that's usually a + machine-generated ghost suggestion, not something you typed. - **It scales past two.** Add `project C`, install the child skill with `{AppName}=projC`, and it joins the same board — no change to the others. diff --git a/herdr/README.md b/herdr/README.md index ed7697f..60d6638 100644 --- a/herdr/README.md +++ b/herdr/README.md @@ -60,6 +60,22 @@ instruction is following orders correctly — that's a fan-out failure, not a ju artifact rule is the backstop for when the fan-out fails anyway, which is why it's the stronger of the two: it doesn't depend on the Coordinator getting the broadcast right. +### Two delivery mechanics the protocol rests on + +The labeling law only works if messages actually arrive. Two things break that silently — both +skills spell them out, and neither is optional reading: + +- **`pane run`, always.** `herdr pane run ""` types the text *and presses Enter*. + `pane send-text` and `agent send` write the characters with **no** Enter, stranding your relay + unsent in the recipient's composer: delivered from your side, invisible from theirs. Most + "the other pane ignored me" incidents are this. (herdr's own `agent --help` says it outright: + *agent send writes literal text; use pane run when you want command text plus Enter*.) +- **The `❯` composer line lies.** Claude Code auto-fills it with a machine-generated *suggested* + prompt derived from that pane's last turn. A plain-text read can't tell it from real unsent + operator input — only `agent read --ansi` can (ghosts are faint `\x1b[2m` grey; real input is + stark white). Never judge a pane's state, its interruptibility, or the Operator's intent from + that line. + ## What's here | Path | Role | Genericized from | Purpose | @@ -100,7 +116,9 @@ Notes: is a custom skill you may or may not have. - The platform gotchas the skills call out (Git Bash mangling a leading `/` into a Windows path; bash command-substituting backticks inside `herdr pane run "..."`) are Windows/Git-Bash - specific — on a pure-POSIX host you can relax them, but they're harmless to keep. + specific — on a pure-POSIX host you can relax them, but they're harmless to keep. The two + delivery mechanics above (`pane run` vs. `send-text`; ghost composer suggestions) are **not** + platform-specific — keep those verbatim. ## Preconditions (all three skills) diff --git a/herdr/child/cross-coordinate/SKILL.md b/herdr/child/cross-coordinate/SKILL.md index df75ad0..e52ce7a 100644 --- a/herdr/child/cross-coordinate/SKILL.md +++ b/herdr/child/cross-coordinate/SKILL.md @@ -88,6 +88,80 @@ every send. --- +## Sending into another pane — the mechanics that make a message land + +Every message in this protocol is **injected input**: you are typing into someone else's live +session. Two failure modes look identical from your side ("I sent it") and from theirs ("nothing +arrived"), so get these right before anything else in this skill. + +### 1. Text without an Enter never submits + +| Command | What it actually does | +|---------|-----------------------| +| `herdr pane run ""` | types the text **and presses Enter** — delivered as a turn | +| `herdr pane send-text ""` | types the text, **no Enter** — it sits unsent in their composer | +| `herdr agent send ""` | same — literal text, **no Enter** | + +**Always `pane run`.** A `send-text` relay strands your message on the recipient's `❯` line, where +it looks to them like something they half-typed and to you like a delivered ask. That is the real +cause of most "the Coordinator never answered me" / "the sibling ignored my relay" reports: the +message was pasted, never submitted. + +Keep every relay to a **single line** so it submits cleanly. If a send genuinely must span lines, +`pane send-text` the block and then `herdr pane send-keys Enter` **once** — but prefer one +line plus a pointer to the durable note. + +### 2. Prose is not a send + +Your ordinary output reaches the **Operator only**. It never appears in another pane. Writing +"I'll let {SiblingApp} know" is not letting them know — the *only* channel to another pane is an +explicit `pane run`. + +### 3. Sends are fire-and-forget + +`pane run` prints nothing on success and there is no ack. A response comes back only as a labeled +inbound turn (`From {SiblingApp}:` / `From Coordinator:`), on the recipient's own timing. For a +send that won't draw a reply — a status ping, a close-out — confirm it landed with +`herdr pane read --source recent --lines 20` rather than assuming. + +### 4. Two characters that mangle the string + +- **Backticks** — bash command-substitutes them inside `herdr pane run "..."`. Write field and + event names as plain text (`user_id` → user_id). +- **A leading `/`** — Git Bash on Windows path-converts it (`/rename` becomes + `C:/Program Files/Git/rename`) and the target receives garbage. Send slash-prefixed payloads via + **PowerShell**, or prefix the bash call with `MSYS_NO_PATHCONV=1`. (Pure-POSIX hosts are fine.) + +--- + +## The `❯` composer line lies — ghost suggestions + +Claude Code auto-populates a pane's composer (`❯`) line with a **suggested next prompt** it +generates from that pane's last turn. Nobody typed it. It reads like plausible Operator input +precisely *because* it's derived from the pane's own context — which is what makes it dangerous +whenever you read a pane, including your own. + +**The tell is colour, and only `--ansi` shows it:** + +- `herdr agent read --source visible` (default `--format text`) **strips ANSI**, so a ghost + suggestion and real unsent input arrive byte-identical. Blind. +- `herdr agent read --source visible --ansi` preserves it: a ghost is wrapped in **`\x1b[2m`** + (SGR faint) plus grey `\x1b[38;2;153;153;153m` (`#999`). **Real typed input is stark white** — + normal intensity, no faint. +- Filter on the **`\x1b[2m`** code, not on the `❯` glyph — the glyph decodes to surrogate bytes and + won't match. + +**Rules:** + +1. Never read the composer line as pane state or as Operator intent. Judge from the **transcript + above the input box** plus `herdr agent get ` / `herdr agent list` `agent_status`. +2. If you must inspect composer content, read with `--ansi` and **discard any faint line**. +3. **Sending is unaffected** — `pane run` types real characters over whatever placeholder is + showing, so you can never accidentally send a ghost and never need to clear one first. Don't + chase it: Escape-then-run races, and the suggestion regenerates anyway. + +--- + ## Outbound flow — raising an ask ### 1. Back it with a durable note (recommended) @@ -120,11 +194,10 @@ turn, the "next convenient interrupt"): herdr pane run "" "From {AppName}: . Needs . Detail in ." ``` -Use `pane run` (text + a real Enter) with a **single-line** message so it submits cleanly. If you -must send multiple lines, `pane send-text` the block then `pane send-keys Enter` once — but -prefer one line + tracker item. **Never put backtick characters inside the `herdr pane run "..."` -string** — bash command-substitutes them and mangles the send. Write field/event names in plain -text (write `user_id` as user_id, not wrapped in backticks). +`pane run` (not `send-text` / `agent send`) with a **single-line**, backtick-free message — see +*Sending into another pane* above for why each of those matters. It's fire-and-forget: nothing +confirms delivery, so if you want certainty the relay landed, `herdr pane read +--source recent` rather than assuming. ### 4. Then wait — do not touch the sibling @@ -142,9 +215,8 @@ your pane clearly labeled — e.g. `From {SiblingApp}:`. That label is how you k **not** the Operator (unlabeled turns are the Operator; a `From Coordinator:` turn is the Coordinator). -- Reply into **their** pane, always prefixed `From {AppName}:`. **Never put backtick characters - inside the `herdr pane run "..."` string** — bash command-substitutes them and mangles the send; - write the message in plain text: +- Reply into **their** pane, always prefixed `From {AppName}:`, via `pane run` and in plain text + (no backticks — see *Sending into another pane*): ```bash herdr pane run "" "From {AppName}: confirmed - the field ships in the frame. Two questions: ..." ``` @@ -192,8 +264,17 @@ read. - Don't open a coordination by messaging a sibling directly — it goes through the Coordinator. - Don't mistake a `From Coordinator:` turn for the Operator — it's the interrupt-buffer, a distinct third class. +- Don't deliver a relay with `pane send-text` or `agent send` — they type the text with **no + Enter**, so it sits unsent in the recipient's composer. `pane run` or it didn't happen. +- Don't treat saying something in prose as messaging another pane — your output goes to the + Operator only; the pane hears nothing without an explicit `pane run`. +- Don't assume a no-reply send (status ping, close-out) landed — `pane read --source recent` the + target if it matters. - Don't put backtick characters inside a `herdr pane run "..."` string — bash will command-substitute them and mangle the relay. +- Don't read anything into another pane's `❯` composer line — it's usually a machine-generated + ghost suggestion, not typed input. Judge by transcript + `agent_status`; if you must look, read + `--ansi` and discard the faint (`\x1b[2m`) lines. - Don't send a fat multi-paragraph relay when you have a tracker — the item carries the detail, the relay is one line + ID. - Don't hand a sibling a bare tracker ID — it's repo-local; give an absolute-path / `--path` diff --git a/herdr/coordinator/cross-coordinate/SKILL.md b/herdr/coordinator/cross-coordinate/SKILL.md index 0fcc54a..79cd609 100644 --- a/herdr/coordinator/cross-coordinate/SKILL.md +++ b/herdr/coordinator/cross-coordinate/SKILL.md @@ -38,6 +38,72 @@ Map by **label** (cwd as tiebreak): `Coordinator` (this pane, cwd = `{workspace- one pane per child app (cwd = `{workspace-root}/`). Take each `pane_id` from the JSON fresh; if labels drifted, re-run rather than reusing an old ID. +## Sending into a child pane — the mechanics that make a message land + +Every relay I inject is **typed input into someone else's live session**. As the router I'm the +pane that sends most, so a silent delivery failure here strands a child that's waiting on me. + +### 1. Text without an Enter never submits + +| Command | What it actually does | +|---------|-----------------------| +| `herdr pane run ""` | types the text **and presses Enter** — delivered as a turn | +| `herdr pane send-text ""` | types the text, **no Enter** — it sits unsent in their composer | +| `herdr agent send ""` | same — literal text, **no Enter** | + +**Always `pane run`.** A `send-text` green-light strands the whole coordination: the requester +waits for a sibling that never got the ask, the target sees stray text on its `❯` line, and my +tracking item says "relayed." Keep every relay to a **single line** so it submits cleanly; if one +genuinely must span lines, `pane send-text` the block then `herdr pane send-keys Enter` +**once**. + +### 2. Prose is not a send + +My ordinary output reaches the **Operator only** — it never appears in a child's pane. Narrating +"green-lighting {AppB} now" is not green-lighting anyone; only an explicit `pane run` is. + +### 3. Sends are fire-and-forget + +`pane run` prints nothing on success and there's no ack. A child's answer comes back only as a +`From :` turn on its own timing. That's fine for an ask (the reply *is* the confirmation), +but for a send that draws no reply — a status note, the Step 2c close-out relay — confirm with +`herdr pane read --source recent --lines 20` before recording it as delivered. + +### 4. Two characters that mangle the string + +- **Backticks** — bash command-substitutes them inside `herdr pane run "..."`. Write field and + event names as plain text (`user_id` → user_id). +- **A leading `/`** — Git Bash on Windows path-converts it (`/rename` becomes + `C:/Program Files/Git/rename`) and the target receives garbage. Send slash-prefixed payloads via + **PowerShell**, or prefix the bash call with `MSYS_NO_PATHCONV=1`. (Pure-POSIX hosts are fine.) + +## The `❯` composer line lies — ghost suggestions + +Claude Code auto-populates a pane's composer (`❯`) line with a **suggested next prompt** it +generates from that pane's last turn. Nobody typed it. It reads like plausible Operator input +precisely *because* it's derived from the pane's own context. This bites me hardest in Step 2a, +where I'm judging whether a child is safe to interrupt: a ghost makes an idle pane look like it +has operator input pending, and a ghost that regenerates looks like a pane I can't get clean. + +**The tell is colour, and only `--ansi` shows it:** + +- `herdr agent read --source visible` (default `--format text`) **strips ANSI**, so a ghost + suggestion and real unsent input arrive byte-identical. Blind. +- `herdr agent read --source visible --ansi` preserves it: a ghost is wrapped in **`\x1b[2m`** + (SGR faint) plus grey `\x1b[38;2;153;153;153m` (`#999`). **Real typed input is stark white** — + normal intensity, no faint. +- Filter on the **`\x1b[2m`** code, not on the `❯` glyph — the glyph decodes to surrogate bytes and + won't match. + +**Rules:** + +1. The composer line is **not** part of the interruptibility test. Classify from the **transcript + above the input box** plus `agent_status` only. +2. If I must inspect composer content, read with `--ansi` and **discard any faint line**. +3. **Sending is unaffected** — `pane run` types real characters over whatever placeholder is + showing, so I can never accidentally send a ghost and never need to clear one first. Don't + chase it: Escape-then-run races, and the suggestion regenerates anyway. + ## Step 1 — Receive & parse the relay From a `From :` turn, pull out: @@ -83,9 +149,11 @@ herdr agent list # target's agent_status herdr agent read --source visible --lines ~20 ``` -Classify what I see: -- **Safe-idle** — `agent_status: idle`, an empty `❯` prompt, no pending question/dialog on - screen → **go now** (Step 2b). +Classify what I see — from the **transcript and `agent_status`**, never from the `❯` composer line +(whatever sits there is almost always a ghost suggestion, not operator input — see *The `❯` +composer line lies*): +- **Safe-idle** — `agent_status: idle`, the last turn visibly finished, no pending + question/dialog on screen → **go now** (Step 2b). - **Working** — `agent_status: working` / actively mid-task → **don't interrupt.** Either hand to the Operator (below) or set a light background re-check (`herdr agent wait --status idle` run in the background, or a short poll loop), then @@ -103,8 +171,9 @@ go-signal to run Step 2b for the buffered ask. ### 2b. Release the ask — relay + green-light -Inject one `From Coordinator:` message into the **target's** pane (via `pane run`; send -slash-free text so Git Bash is fine, but PowerShell is always safe). It must carry: +Inject one `From Coordinator:` message into the **target's** pane — one line, via `pane run` so it +actually submits, backtick-free, PowerShell-sent if it carries a leading `/` or a Windows path +(see *Sending into a child pane*). It must carry: 1. **Who's seeking help** — the requesting child's name **and pane_id**. 2. **The request summary** — the one-line ask. @@ -220,6 +289,14 @@ the two: it doesn't depend on me getting the fan-out right. Operator's. - Don't interrupt a `working` or awaiting-input target on my own judgment — safe-idle only; when murky, hand the timing to the Operator and wait for "now is a good time." +- Don't judge interruptibility (or Operator intent) from a pane's `❯` composer line — it's + normally a machine-generated ghost suggestion. Transcript + `agent_status` only; if I must look, + read `--ansi` and discard the faint (`\x1b[2m`) lines. +- Don't deliver a relay with `pane send-text` or `agent send` — no Enter means it sits unsent in + the target's composer while I record it as relayed. `pane run` or it didn't happen. +- Don't count narrating a green-light as sending one — prose reaches the Operator, not the pane. +- Don't record a no-reply send (a close-out, a status note) as delivered without a + `pane read --source recent` on the target. - Don't forget the three reminders in the green-light message: pane of the requester, `From :` role-play, and check back with me on completion. - Don't cite a cross-repo tracker item as a bare ID in a relay — the target's tracker is local diff --git a/herdr/coordinator/set-workspace/SKILL.md b/herdr/coordinator/set-workspace/SKILL.md index 37f185a..a4b8a02 100644 --- a/herdr/coordinator/set-workspace/SKILL.md +++ b/herdr/coordinator/set-workspace/SKILL.md @@ -71,10 +71,16 @@ herdr pane rename "{AppB}" ## Step 3 — Drive each agent's Claude-side setup Inject these as real typed input via `herdr pane run ""` — one call per -line, in order. **On Windows, send them through the PowerShell tool** (or Bash prefixed with -`MSYS_NO_PATHCONV=1`): Git Bash mangles a leading `/` into a Windows path -(`/rename` → `C:/Program Files/Git/rename`) and the command arrives as garbage. (Pure-POSIX -hosts don't have this trap.) +line, in order. Two things about that: + +- **`pane run` is the only form that submits.** It types the text *and* presses Enter; + `pane send-text` / `agent send` write the characters with **no** Enter, leaving the slash + command sitting unexecuted in the composer. A pane that "ignored" its `/rename` was almost + always sent without an Enter. +- **On Windows, send them through the PowerShell tool** (or Bash prefixed with + `MSYS_NO_PATHCONV=1`): Git Bash mangles a leading `/` into a Windows path + (`/rename` → `C:/Program Files/Git/rename`) and the command arrives as garbage. (Pure-POSIX + hosts don't have this trap.) Per child pane, in order — pick a distinct `/color` for each so panes are visually separable: @@ -102,6 +108,12 @@ Send each pane's commands sequentially, with a short beat (~0.5s) between them s `herdr pane send-keys Escape` so the pane returns to a clean `❯` prompt — do this before Step 4, since an open RC panel will eat the next injected input. +**A caveat on reading `❯` here:** Claude Code auto-fills the composer with a *suggested* next +prompt derived from that pane's last turn. It is not operator input and does not mean the pane is +dirty or busy — a plain-text read can't distinguish it (only `herdr agent read --ansi` can; +ghosts are faint `\x1b[2m` grey, real input is stark white). Don't try to clear one before +injecting: `pane run` types over any placeholder, so just send. + Because `/rc` opens the panel, keep it **last** of the three per pane (as ordered above), and Esc-dismiss it. No verification pass on rename/color — fire and move on.