diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md
index 66eccd892..000735fc1 100644
--- a/packages/coding-agent/CHANGELOG.md
+++ b/packages/coding-agent/CHANGELOG.md
@@ -23,6 +23,7 @@
- Changed `/debug` handling in interactive mode so `/debug` with arguments now executes the requested debug subcommand instead of always opening the debug selector
- Changed `/debug dump-next-request` path handling to expand `~` and resolve relative paths against the current working directory
- Changed the task tool's TUI block: the header now shows the task dispatch glyph (`tool.task`) while agents are in flight instead of a spinner (async spawns return immediately, so a spinner misread the call as blocking), and per-agent rows use one static dot for every state — completed rows keep the same dot and settle from accent to the plain foreground color instead of switching to a different status glyph
+- Compressed the `eval`, `browser`, `read`, and `irc` tool prompts (6156 → 4932 tokens o200k, −20%): deduplicated claims across sections, tightened helper reference descriptions, trimmed redundant examples; input grammar, examples, and template conditionals (`py`/`js`/`spawns`, read display modes) unchanged
### Fixed
diff --git a/packages/coding-agent/src/prompts/tools/browser.md b/packages/coding-agent/src/prompts/tools/browser.md
index 7c3a3fa7d..a2c936eaa 100644
--- a/packages/coding-agent/src/prompts/tools/browser.md
+++ b/packages/coding-agent/src/prompts/tools/browser.md
@@ -1,40 +1,39 @@
Drives real Chromium tab; full puppeteer access via JS execution.
-- For static web content (articles, docs, issues/PRs, JSON, PDFs, feeds), prefer `read` tool with URL — reader-mode text without spinning up browser. Use this tool when you need JS execution, authentication, or interactive actions.
-- Three actions only:
- - `open` — acquire or reuse named tab. `name` defaults `"main"`. Optional `url` navigates after tab ready. Optional `viewport` sets dimensions. Optional `dialogs: "accept" | "dismiss"` auto-handles `alert`/`confirm`/`beforeunload` so navigation/clicks don't hang; by default dialogs are unhandled and the page hangs until you wire `page.on('dialog', …)`.
- - `close` — release tab by `name`, or every tab with `all: true`. For spawned-app browsers, set `kill: true` to terminate process tree (default leaves running).
- - `run` — execute JS against existing tab. `code` is body of async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. Function's return value JSON-stringified into tool result; multiple `display(value)` calls accumulate text/images.
-- Tabs survive across `run` calls and across in-process subagents. Open once, reuse many times.
-- Browser kinds, selected by `app` field on `open`:
+- Static content (articles, docs, issues/PRs, JSON, PDFs, feeds)? Use `read` with the URL. Reach for browser only for JS execution, authentication, or interactive actions.
+- Three actions:
+ - `open` — acquire or reuse named tab (`name` defaults `"main"`). Optional `url` (navigate once ready), `viewport`, `dialogs: "accept" | "dismiss"` (auto-handle `alert`/`confirm`/`beforeunload`; unhandled dialogs hang the page until you wire `page.on('dialog', …)`).
+ - `close` — release tab by `name`, or every tab with `all: true`. `kill: true` also terminates spawned-app process trees (default leaves them running).
+ - `run` — execute JS in an existing tab. `code` is the body of an async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. Return value is JSON-stringified into the result; `display(value)` calls accumulate text/images.
+- Tabs survive across `run` calls and in-process subagents — open once, reuse.
+- Browser kinds (`app` field on `open`):
- default (no `app`) → headless Chromium with stealth patches.
- - `app.path` → spawn absolute binary (Electron/CDP); a running instance with an open CDP port is reused. No stealth patches — NEVER tamper with real desktop app.
+ - `app.path` → spawn absolute binary (Electron/CDP); a running instance with an open CDP port is reused. No stealth patches — NEVER tamper with a real desktop app.
- `app.cdp_url` → connect to existing CDP endpoint (e.g. `http://127.0.0.1:9222`).
- - `app.target` (with `path`/`cdp_url`) — substring matched against url+title to pick BrowserWindow when app exposes several.
-- Inside `run`, `tab` exposes high-level helpers; reach for `page` (raw puppeteer Page) when you need anything they don't cover.
- - `tab.goto(url, { waitUntil? })` — clears element cache and navigates.
- - `tab.observe({ includeAll?, viewportOnly? })` — accessibility snapshot. Returns `{ url, title, viewport, scroll, elements: [{ id, role, name, value, states, … }] }`. Element ids stable until next observe/goto.
- - `tab.id(n)` — resolves element id from most recent observe to real `ElementHandle` you can `.click()`, `.type()`, etc.
- - `tab.click(selector)` / `tab.type(selector, text)` / `tab.fill(selector, value)` / `tab.press(key, { selector? })` / `tab.scroll(dx, dy)` — selector-based actions.
- - `tab.waitFor(selector)` — waits until selector attached, returns resolved `ElementHandle` for chaining (e.g. `const btn = await tab.waitFor('text/Submit'); await btn.click();`).
- - `tab.drag(from, to)` — drag from one point to another. Each endpoint either selector string (drag center-to-center) or `{ x, y }` viewport-coordinate point (for canvases, sliders).
- - `tab.scrollIntoView(selector)` — scroll matching element to center of viewport (use before clicking off-screen elements).
- - `tab.select(selector, …values)` — set selected option(s) on `
-- MUST call `open` before `run`. `run` does not implicitly create tab.
-- NEVER screenshot just to "see what's on page" — `tab.observe()` returns structured data with element ids you can act on immediately.
-- After `tab.goto()` or any navigation, prior element ids from `tab.observe()` invalidated. Re-observe before referencing them.
+- MUST `open` before `run` — `run` never creates a tab.
+- Default to `tab.observe()` for page state — structured data with actionable element ids. Screenshot ONLY when visual appearance matters.
+- Navigation invalidates element ids — re-observe before using them.
- `code` runs with full Node access. Treat as your code, not sandboxed code.
@@ -46,28 +45,19 @@ Drives real Chromium tab; full puppeteer access via JS execution.
# Click an observed element by id
`{"action":"run","name":"docs","code":"const obs = await tab.observe(); const link = obs.elements.find(e => e.role === 'link' && e.name === 'Sign in'); assert(link, 'Sign in link missing'); await (await tab.id(link.id)).click();"}`
-# Screenshot to look at the page — no save path
-`{"action":"run","name":"docs","code":"await tab.screenshot();"}`
-
-# Keep a full-page screenshot on disk for a later step
-`{"action":"run","name":"docs","code":"await tab.screenshot({ fullPage: true, save: 'screenshot.png' });"}`
-
# Fill and submit a form via selectors
`{"action":"run","name":"docs","code":"await tab.fill('input[name=email]', 'me@example.com'); await tab.click('text/Continue');"}`
+# Screenshot to look at the page — no save path
+`{"action":"run","name":"docs","code":"await tab.screenshot();"}`
+
# Attach to an existing Electron app
`{"action":"open","name":"cursor","app":{"path":"/Applications/Cursor.app/Contents/MacOS/Cursor"}}`
-# Close one tab (browser stays alive if other tabs reference it)
-`{"action":"close","name":"docs"}`
-
-# Close every tab; leave spawned apps running
-`{"action":"close","all":true}`
-
-# Close every tab and kill spawned-app processes too
+# Close every tab and kill spawned-app processes
`{"action":"close","all":true,"kill":true}`
diff --git a/packages/coding-agent/src/prompts/tools/eval.md b/packages/coding-agent/src/prompts/tools/eval.md
index 52f25f101..e4b959576 100644
--- a/packages/coding-agent/src/prompts/tools/eval.md
+++ b/packages/coding-agent/src/prompts/tools/eval.md
@@ -1,92 +1,69 @@
Run code in a persistent kernel using a list of cells.
-Each call submits one or more cells. Cells run in array order. State persists within each language — across cells, tool calls, and subagents spawned with `task`: variables a parent or subagent declares are visible to the other. Lean on this: stage helpers, loaded datasets, or live clients once, then fan out `task` subagents that use them directly. No re-importing, re-fetching, or serializing across the boundary.
+Cells run in array order. State persists per language — across cells, tool calls, and `task` subagents: variables either side defines are visible to the other. Stage helpers, datasets, or live clients once; subagents use them directly — no re-importing or serializing across the boundary.
Cell fields:
- `language` — {{#if py}}`"py"` for the IPython kernel{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`"js"` for the persistent JavaScript VM{{/if}}.
-- `code` — cell body, verbatim. Newlines, quotes, and indentation are JSON-encoded; no fences, no headers.
-- `title` (optional) — short label shown in the transcript (e.g. `"imports"`, `"load config"`).
-- `timeout` (optional) — per-cell wall-clock budget in seconds (1-3600). Default 30. It bounds the cell's **own** work: compute, `print`/stdout, `log()`/`phase()`, and ordinary tool calls all count. The clock pauses while an `agent()`/`parallel()`/`completion()` call is in flight, so long fanouts and slow completions never need a raised `timeout`. Raise it only for heavy local work or long non-agent tool calls.
-- `reset` (optional) — wipe this cell's language kernel before running.{{#ifAll py js}} Reset is per-language: a `py` cell's reset does not touch the JavaScript VM and vice versa.{{/ifAll}}
+- `code` — cell body, verbatim. Newlines and quotes JSON-encoded; no fences, no headers.
+- `title` (optional) — short transcript label (e.g. `"imports"`).
+- `timeout` (optional) — per-cell seconds (1-3600, default 30). Bounds the cell's own work only; the clock pauses while `agent()`/`parallel()`/`completion()` calls are in flight, so fanouts never need a raise. Raise only for heavy local compute or long non-agent tool calls.
+- `reset` (optional) — wipe this cell's language kernel first.{{#ifAll py js}} Per-language: a `py` reset never touches the JS VM.{{/ifAll}}
-**Work incrementally:**
-
-- One logical step per cell (imports, define, test, use).
-- Pass multiple small cells in one call.
-- Define small reusable functions for individual debugging.
-- Put workflow explanations in the assistant message or `title` — never inside cell code.
-{{#if py}}- Python cells run inside an IPython kernel with a live event loop. Use top-level `await` directly (e.g. `await main()`); `asyncio.run(…)` raises "cannot be called from a running event loop".{{/if}}
-**On failure:** errors identify the failing cell (e.g., "Cell 3 failed"). Resubmit only the fixed cell (or fixed cell + remaining cells).
+Work incrementally: one logical step per cell (imports, define, test, use); pass multiple small cells per call; define small reusable functions for individual debugging. Workflow explanations go in the assistant message or `title`, never inside cell code.
+{{#if py}}Python runs in IPython with a live event loop: use top-level `await` directly; `asyncio.run(…)` raises "cannot be called from a running event loop".{{/if}}
+On failure, errors name the failing cell ("Cell 3 failed") — resubmit only the fixed cell (plus any remaining).
-{{#ifAll py js}}Same helpers in both runtimes with the same positional argument order. Python: trailing options as keyword args. JavaScript: trailing options are a single trailing object literal, never positional — passing options positionally (or any extra positional arg) throws. JavaScript helpers are async and `await`able; Python helpers run synchronously.{{else}}{{#if py}}Helpers run synchronously. Trailing options are keyword arguments.{{/if}}{{#if js}}Helpers are async and `await`able. Trailing options are a single trailing object literal, never positional — passing options positionally (or any extra positional arg) throws.{{/if}}{{/ifAll}}
+{{#ifAll py js}}Same helpers in both runtimes, same positional order. Python: helpers run synchronously; trailing options are keyword args. JavaScript: helpers are async and `await`able; trailing options are ONE trailing object literal, never positional (extra positional args throw).{{else}}{{#if py}}Helpers run synchronously. Trailing options are keyword arguments.{{/if}}{{#if js}}Helpers are async and `await`able. Trailing options are ONE trailing object literal, never positional (extra positional args throw).{{/if}}{{/ifAll}}
```
display(value) → None
- Render a value in the current cell output.
+ Render value in cell output, shows presentable values natively (figures, images, dataframes)
print(value, ...) → None
- Print to the cell's text output.
+ Print to text output.
read(path, offset?=1, limit?=None) → str
- Read file contents as text. offset/limit are 1-indexed line bounds. Accepts `local://…` (resolved to the session-local root, same place `read local://…` reads).
+ Read file as text; offset/limit are 1-indexed lines. Accepts `local://…`.
write(path, content) → str
- Write content to a file (creates parent directories). Returns the resolved path. Accepts `local://…` to persist artifacts across turns / share with subagents.
+ Write file (creates parents); returns resolved path. `local://…` persists across turns / subagents.
append(path, content) → str
- Append content to a file. Returns the resolved path. Accepts `local://…`.
+ Append to file; returns resolved path. Accepts `local://…`.
tree(path?=".", max_depth?=3, show_hidden?=False) → str
- Render a directory tree.
+ Directory tree.
diff(a, b) → str
- Unified diff between two files.
+ Unified diff of two files.
env(key?=None, value?=None) → str | None | dict
- No args → full environment as dict. One arg → value of `key`. Two args → set `key=value` and return value.
+ No args → full env dict; one → value of `key`; two → set `key=value`, return value.
output(*ids, format?="raw", query?=None, offset?=None, limit?=None) → str | dict | list[dict]
- Read task/agent output by ID. Single id returns text/dict; multiple ids return a list.
+ Read task/agent output by id; one id → text/dict, multiple → list.
tool.(args) → unknown
- Invoke any session tool by name. `args` is the tool's parameter object.
+ Invoke any session tool; `args` is its parameter object.
completion(prompt, model?="default", system?=None, schema?=None) → str | dict
- Oneshot, stateless completion (no history, no tools). `model` picks a tier: "smol" (fast), "default" (this session's model), "slow" (most capable). Pass `system` for a system prompt. Pass a JSON-Schema `schema` to force structured output and get the parsed object back; otherwise returns the completion text.
+ Oneshot stateless completion (no history, no tools). `model` tier: "smol" (fast) | "default" (session model) | "slow" (most capable). JSON-Schema `schema` forces structured output, returns parsed object.
{{#if spawns}}agent(prompt, agent_type?="task", model?=None, label?=None, schema?=None) → str | dict
- Run a subagent and return its final output. Defaults to the bundled "task" agent; pass `agent_type`/`agentType` for another discovered agent. Pass a JSON-Schema `schema` to force structured output and get the parsed object back. Share background by writing a `local://` file and referencing it in the prompt.
-{{#if js}} In JS, pass options as one trailing object — never positional: agent(prompt, { agentType, schema }).
+ Run a subagent, return its final output. `agent_type`/`agentType` picks another discovered agent; `schema` as in completion(). Share background via `local://` files referenced in the prompt.
+{{#if js}} JS: options are ONE trailing object — agent(prompt, { agentType, schema }).
{{/if}}
{{/if}}
parallel(thunks) → list
- Run thunks (callables) through a bounded pool, preserving input order. The pool is as wide as a `task` tool batch, so fan out as wide as the work divides — don't pre-shrink it. Barrier: returns once all finish; a thunk that throws propagates.
+ Run thunks through a bounded pool (as wide as a `task` batch — don't pre-shrink), preserving input order. Barrier: returns when all finish; a throwing thunk propagates.
pipeline(items, ...stages) → list
- Map each item through stages left-to-right; a barrier runs between stages (every item clears stage N before stage N+1). Each stage is a one-arg callable: stage 1 gets the original item, later stages get the previous result. Same pool width as parallel().
+ Map items through one-arg stages left-to-right, barrier between stages; stage 1 gets the item, later stages the previous result. Same pool width as parallel().
log(message) → None
- Emit a progress line above the status tree.
+ Progress line above the status tree.
phase(title) → None
- Start a phase; the status lines that follow group under it.
+ Start a phase grouping subsequent status lines.
budget → per-turn token budget
- {{#if py}}`budget.total` (ceiling or None), `budget.spent()` (output tokens this turn), `budget.remaining()` (math.inf when no ceiling), `budget.hard` (bool).{{/if}}{{#if js}}`await budget.total()` (ceiling or null), `await budget.spent()`, `await budget.remaining()` (Infinity when no ceiling), `await budget.hard()`.{{/if}} A ceiling is set by a `+Nk` message directive (advisory) or `+Nk!`/Goal Mode (hard — `agent()` refuses to spawn past it); otherwise total is None/null and spend is still tracked across the turn (main loop + eval subagents).
+ {{#if py}}`budget.total` (ceiling or None), `budget.spent()`, `budget.remaining()` (math.inf when no ceiling), `budget.hard` (bool).{{/if}}{{#if js}}`await budget.total()` (ceiling or null), `await budget.spent()`, `await budget.remaining()` (Infinity when no ceiling), `await budget.hard()`.{{/if}} Ceiling comes from a `+Nk` directive (advisory) or `+Nk!`/Goal Mode (hard — `agent()` refuses to spawn past it); otherwise None/null, spend still tracked across the turn.
```
-
-
-
-{{#if js}}- **js**: the VM exposes a selective `process` subset, Web APIs, `Buffer`, `fs/promises`, and the `Bun` global.
-{{/if}}
-
-{{#if py}}```json
{
"cells": [
{ "language": "py", "title": "imports", "timeout": 10, "code": "import json\nfrom pathlib import Path" },
{ "language": "py", "title": "load config", "code": "data = json.loads(read('package.json'))\ndisplay(data)" }
]
}
-```{{/if}}{{#ifAll py js}}
-
-{{/ifAll}}{{#if js}}```json
-{
- "cells": [
- { "language": "js", "title": "summary", "reset": true, "code": "const data = JSON.parse(await read('package.json'));\ndisplay(data);\nreturn data.name;" }
- ]
-}
-```{{/if}}
diff --git a/packages/coding-agent/src/prompts/tools/irc.md b/packages/coding-agent/src/prompts/tools/irc.md
index 02afc4434..b202daed9 100644
--- a/packages/coding-agent/src/prompts/tools/irc.md
+++ b/packages/coding-agent/src/prompts/tools/irc.md
@@ -1,55 +1,53 @@
Sends short text messages to other agents in this process and receives theirs.
-- The main agent is addressable as `Main`. Subagents reuse their task id (e.g. `AuthLoader`, or `AuthLoader-2` when the name repeats).
-- `op: "list"` — every addressable peer with status (`running` | `idle` | `parked`), unread count, parent, and last activity. Use it before sending if you are not sure who exists.
-- `op: "send"` — fire-and-forget delivery of `message` to `to` (a peer id, or `"all"` to broadcast to live peers). Returns per-recipient receipts immediately; it NEVER waits for the recipient to act. Receipt outcomes: `injected` (recipient was mid-turn; message folded in at their next step boundary), `woken` (idle recipient started a turn), `revived` (parked recipient was brought back and woken), `failed`.
-- Messaging an `idle` or `parked` peer is how you wake it — there is no separate revive call.
-- `send` with `await: true` — convenience round-trip: send, then block until the next message from that peer arrives (or the timeout passes). Invalid with `to: "all"`.
-- `op: "wait"` — block until a message arrives (optionally only `from` a specific peer); consumes and returns it. A timeout is a clean "no message" result, not an error.
-- `op: "inbox"` — drain pending messages without blocking (`peek: true` to leave them unread).
-- `replyTo` — set it to the id of the message you are answering so the sender can correlate.
-- Nobody answers on a peer's behalf — a reply normally arrives only when the recipient sends one — with one exception: `send` with `await: true` to a peer that is mid-turn and cannot reach a step boundary (async execution disabled, e.g. blocked in a synchronous task spawn) gets a side-channel auto-reply generated from that peer's context. For background on what a peer has been doing, `read` `history://` instead of interrogating them.
+- Main agent is `Main`; subagents reuse their task id (`AuthLoader`, or `AuthLoader-2` when the name repeats).
+- `op: "list"` — peers with status (`running` | `idle` | `parked`), unread count, parent, last activity. Use when unsure who exists.
+- `op: "send"` — fire-and-forget `message` to `to` (peer id, or `"all"` to broadcast to live peers). Returns per-recipient receipts immediately; NEVER waits for the recipient to act. Outcomes: `injected` (mid-turn; folded in at next step boundary), `woken` (idle peer started a turn), `revived` (parked peer brought back and woken), `failed`.
+- Messaging an `idle`/`parked` peer is how you wake it — there is no separate revive call.
+- `send` + `await: true` — round-trip: send, then block until that peer's next message (or timeout). Invalid with `to: "all"`.
+- `op: "wait"` — block until a message arrives (optionally only `from` one peer); consumes and returns it. Timeout = clean "no message", not an error.
+- `op: "inbox"` — drain pending messages without blocking (`peek: true` leaves them unread).
+- `replyTo` — id of the message you are answering, so the sender can correlate.
+- Replies arrive only when the recipient sends one. Exception: `await: true` to a peer stuck mid-turn (async execution disabled, e.g. blocked in a synchronous task spawn) gets a side-channel auto-reply from its context. For background on a peer, `read` `history://` instead of interrogating it.
-You SHOULD reach for `irc` proactively when continuing alone is wasteful or wrong. When in doubt, prefer messaging.
-- **Unexpected state.** The task did not describe what you found — missing file, config contradicting the assignment, API or tool behaving differently than told. DM `Main` (or the spawning agent) instead of guessing.
-- **Blocked by another agent.** A peer holds the file/branch/resource you need, started the change you are about to make, or owns a decision you depend on. DM that peer (or broadcast to discover who) before duplicating work.
-- **Decision points outside your scope.** A genuine fork the assignment did not pre-decide (e.g. which of two viable APIs, whether to refactor adjacent code). Ask the requester rather than picking unilaterally.
-- **Coordination opportunities.** A peer's in-flight work would benefit from yours, or vice-versa.
+Reach for `irc` proactively when continuing alone is wasteful or wrong; when in doubt, message.
+- **Unexpected state** — missing file, config contradicting the assignment, API/tool behaving differently than told. DM `Main` (or your spawner) instead of guessing.
+- **Blocked by another agent** — a peer holds the file/branch/resource or decision you need, or started the change you're about to make. DM them (or broadcast to discover who) before duplicating work.
+- **Decision outside your scope** — a genuine fork the assignment didn't pre-decide. Ask the requester rather than picking unilaterally.
+- **Coordination** — a peer's in-flight work would benefit from yours, or vice-versa.
-NEVER use `irc` for: routine progress updates, things a tool call can verify, or questions already answered by your assignment / repo / docs.
+NEVER for: routine progress updates, things a tool call can verify, questions your assignment/repo/docs already answer.
-These rules apply to both sending and replying.
-- **Plain prose only.** NEVER send structured JSON status payloads (e.g. `{"type":"task_completed",…}`). Write a normal sentence: "Done with the auth refactor — left a TODO in `src/server/auth.ts` for the rate limiter."
-- **NEVER quote the message you are replying to.** Lead with the answer; set `replyTo` instead.
-- **Use IRC, not terminal tools, to learn about peers.** NEVER `grep` artifacts, read other sessions' JSONL files, or shell-poke to figure out what another agent is doing. DM them, or `read` `history://`.
-- **Send, then keep working.** `send` returns immediately — only `wait` (or `await: true`) when you genuinely cannot proceed without the answer. NEVER follow up with "did you get my message?"; a `failed` receipt means the peer is unreachable — move on or report the blocker; NEVER retry in a loop.
-- **Answer when a response is expected.** When an incoming message asks something, reply with `irc send` to the sender (you may finish your current step first).
-- **Stay terse.** A DM is a chat message, not a memo. One question per send. Share file paths and artifacts via `local://` / `memory://` / `artifact://` URLs instead of pasting blobs.
-- **Address peers by id.** Use the exact id from `op: "list"` (e.g. `AuthLoader`, `Main`). NEVER invent friendly names.
-- **NEVER IRC for things a tool would answer.** If a `read`, `grep`, or build command resolves the question, do that first.
+Applies to sending and replying.
+- **Plain prose only.** NEVER JSON status payloads like `{"type":"task_completed",…}` — write a normal sentence.
+- **NEVER quote the message you answer.** Lead with the answer; set `replyTo`.
+- **Learn about peers via IRC** — NEVER grep artifacts, read other sessions' JSONL, or shell-poke. DM them, or `read` `history://`.
+- **Send, then keep working.** `wait`/`await: true` only when you genuinely cannot proceed. NEVER "did you get my message?". A `failed` receipt = peer unreachable — move on; NEVER retry in a loop.
+- **Answer expected questions** via `irc send` to the sender (finishing your current step first is fine).
+- **Stay terse.** One question per send; share files via `local://`/`memory://`/`artifact://` URLs, never pasted blobs.
+- **Address peers by exact id** from `op: "list"` (e.g. `AuthLoader`, `Main`). NEVER invent friendly names.
+- **NEVER IRC what a tool answers.** A `read`, grep, or build resolves it? Do that first.
# List peers
`{"op": "list"}`
-# Fire-and-forget DM — keep working, check inbox later
-`{"op": "send", "to": "AuthLoader", "message": "Are you still touching src/server/auth.ts? I need to add a 401 path."}`
+# Fire-and-forget DM — same send wakes idle/parked peers
+`{"op": "send", "to": "AuthLoader", "message": "Still touching src/server/auth.ts? I need to add a 401 path."}`
# Round-trip when you cannot proceed without the answer
-`{"op": "send", "to": "Main", "message": "Should I prefer JWT or session cookies for the auth flow?", "await": true}`
-# Wake a parked agent (same send — the bus revives it)
-`{"op": "send", "to": "SchemaMigrator", "message": "The users table changed again; please re-check your migration."}`
+`{"op": "send", "to": "Main", "message": "JWT or session cookies for the auth flow?", "await": true}`
# Block until a specific peer answers
`{"op": "wait", "from": "AuthLoader", "timeoutMs": 60000}`
# Drain pending messages
diff --git a/packages/coding-agent/src/prompts/tools/read.md b/packages/coding-agent/src/prompts/tools/read.md
index b95a1a36c..b452f59a4 100644
--- a/packages/coding-agent/src/prompts/tools/read.md
+++ b/packages/coding-agent/src/prompts/tools/read.md
@@ -1,84 +1,78 @@
Read files, directories, archives, SQLite databases, images, documents, internal resources, and web URLs through a single `path` string.
-- One tool for filesystem, archives, SQLite, images, documents (PDF/DOCX/PPTX/XLSX/RTF/EPUB/ipynb), internal URIs, and web URLs (reader-mode by default).
- You SHOULD parallelize independent reads when exploring related files.
-- You SHOULD reach for `read` — not a browser/puppeteer tool — for fetching web content.
+- You SHOULD reach for `read` — not a browser/puppeteer tool — for web content; browser only when `read` cannot deliver it.
## Parameters
-- `path` — required. Local path, internal URI (`skill://`, `agent://`, `artifact://`, `history://`, `memory://`, `rule://`, `local://`, `vault://`, `mcp://`, `omp://`, `issue://`, `pr://`), or URL. Append `:` for line ranges, raw mode, or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).
+- `path` — required. Local path, internal URI (`skill://`, `agent://`, `artifact://`, `history://`, `memory://`, `rule://`, `local://`, `vault://`, `mcp://`, `omp://`, `issue://`, `pr://`), or URL. Append `:` for line ranges or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).
## Selectors
-Append `:` to `path`. The bare path falls back to the default mode.
+Append `:` to `path`; bare path = default mode.
-- _(none)_ — parseable code → structural summary (signatures kept, bodies elided); other files → read from the start (up to {{DEFAULT_LIMIT}} lines).
-- `:50` / `:50-` — read from line 50 onward.
+- _(none)_ — parseable code → structural summary; other files → from start (up to {{DEFAULT_LIMIT}} lines).
+- `:50` / `:50-` — from line 50 onward.
- `:50-200` — lines 50–200 inclusive.
-- `:50+150` — 150 lines starting at line 50.
-- `:20+1` — anchor on line 20 (single-range reads expand by ≤1 leading and ≤3 trailing context lines).
-- `:5-16,960-973` — multiple ranges in one call (sorted, overlaps merged). Multi-range mode returns exact bounds with no context padding.
-- `:raw` — verbatim text; no anchors, no summary, no line prefixes.
-- `:2-4:raw` or `:raw:2-4` — range AND verbatim; the two compose in either order.
-- `:conflicts` — one-line-per-block index of every unresolved git merge conflict.
+- `:50+150` — 150 lines starting at 50.
+- `:20+1` — anchor line 20 (single-range reads pad ≤1 leading / ≤3 trailing context lines).
+- `:5-16,960-973` — multiple ranges in one call (sorted, overlaps merged); exact bounds, no padding.
+- `:raw` — verbatim; no anchors, no summary, no line prefixes.
+- `:2-4:raw` / `:raw:2-4` — range AND verbatim; compose in either order.
+- `:conflicts` — one line per unresolved git merge conflict block.
# Files
-- Reading a directory path returns a depth-limited dirent listing.
+- Directory path → depth-limited dirent listing.
{{#if IS_HL_MODE}}
-- Reading a file with an explicit selector emits a file snapshot tag header and numbered lines: `[src/foo.ts#1A2B]` then `41:def alpha():`. Copy the `[PATH#TAG]` header for anchored edits; ops use bare line numbers. NEVER fabricate the tag.
+- File with explicit selector → snapshot tag header + numbered lines: `[src/foo.ts#1A2B]` then `41:def alpha():`. Copy the `[PATH#TAG]` header for anchored edits; ops use bare line numbers. NEVER fabricate the tag.
{{else}}
{{#if IS_LINE_NUMBER_MODE}}
-- Reading a file with an explicit selector returns lines prefixed with line numbers: `41|def alpha():`.
+- File with explicit selector → lines prefixed with numbers: `41|def alpha():`.
{{/if}}
{{/if}}
-- Parseable code without a selector returns a **structural summary**: declarations kept, large bodies collapsed to `..` (merged brace pair) or `…` (standalone). Summarized output ends with a footer demonstrating the multi-range selector you can use to recover the elided bodies, e.g.:
-
- `[NN lines elided; re-read needed ranges, e.g. :5-16,40-80]`
-
- Re-issue **only the relevant range(s)** using the multi-range selector (e.g. `:5-16,120-200`). NEVER guess what's inside `..` / `…` — those markers carry no content. NEVER re-read the whole file or use `:raw` when targeted ranges suffice.
+- Parseable code without selector → **structural summary**: declarations kept, bodies collapsed to `..` (merged brace pair) or `…` (standalone). The footer shows the recovery selector: `[NN lines elided; re-read needed ranges, e.g. :5-16,40-80]`. Re-issue ONLY the ranges you need via the multi-range selector. `..`/`…` carry no content — NEVER guess what's inside; NEVER re-read the whole file or `:raw` when ranges suffice.
# Documents & Notebooks
-Extracts text from PDF, Word, PowerPoint, Excel, RTF, and EPUB. Notebooks (`.ipynb`) are shown as editable `# %% [type] cell:N` text; edits round-trip back to the underlying JSON preserving notebook metadata. Add `:raw` to a notebook to bypass the converter and read the JSON directly.
+PDF, Word, PowerPoint, Excel, RTF, EPUB → extracted text. Notebooks (`.ipynb`) → editable `# %% [type] cell:N` text; edits round-trip to the underlying JSON preserving metadata. `:raw` bypasses the converter.
# Images
{{#if INSPECT_IMAGE_ENABLED}}
-Reading an image path returns metadata (mime, bytes, dimensions, channels, alpha). For actual visual analysis, call `inspect_image` with the path and a question describing what to inspect.
+Image path → metadata (mime, bytes, dimensions, channels, alpha). For visual analysis, call `inspect_image` with the path and a question.
{{else}}
-Reading an image path returns the decoded image inline (PNG, JPEG, GIF, WEBP) for direct visual analysis.
+Image path → decoded image inline (PNG, JPEG, GIF, WEBP) for direct visual analysis.
{{/if}}
# Archives
-Supports `.tar`, `.tar.gz`, `.tgz`, `.zip`. Use `archive.ext:path/inside/archive` to read a member, and append a normal selector to the inner path: `archive.zip:dir/file.ts:50-60`.
+`.tar`, `.tar.gz`, `.tgz`, `.zip`. `archive.ext:path/inside/archive` reads a member; inner paths take normal selectors: `archive.zip:dir/file.ts:50-60`.
# SQLite
For `.sqlite`, `.sqlite3`, `.db`, `.db3`:
-- `file.db` — list tables with row counts
+- `file.db` — tables with row counts
- `file.db:table` — schema + sample rows
-- `file.db:table:key` — single row by primary key
-- `file.db:table?limit=50&offset=100` — paginated rows
-- `file.db:table?where=status='active'&order=created:desc` — filtered rows
-- `file.db?q=SELECT …` — read-only SELECT query
+- `file.db:table:key` — row by primary key
+- `file.db:table?limit=50&offset=100` — pagination
+- `file.db:table?where=status='active'&order=created:desc` — filter/order
+- `file.db?q=SELECT …` — read-only SELECT
# URLs
-- Default reader-mode: HTML pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs → clean text/markdown.
-- `:raw` returns untouched HTML; line selectors (`:50`, `:50-100`, `:50+150`) paginate the cached fetched output.
-- Bare `host:port` URLs collide with the selector grammar — add a trailing slash before the selector: `https://example.com/:80`.
+- Reader-mode by default: HTML, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs → clean text/markdown.
+- `:raw` → untouched HTML; line selectors (`:50`, `:50-100`, `:50+150`) paginate the cached fetch.
+- Bare `host:port` collides with the selector grammar — add a trailing slash: `https://example.com/:80`.
# Internal URIs
-`skill://`, `agent://`, `artifact://`, `history://`, `memory://root`, `rule://`, `local://.md`, `vault:///`, `mcp://`, `omp://.md`, `issue://`, and `pr://` resolve transparently and accept the same line selectors as filesystem paths. Use `artifact://` to recover full output that a previous bash/eval/tool result spilled or truncated. `history://` is an agent's transcript as concise markdown; bare `history://` lists agents.
+All `path` URI schemes resolve transparently and take the same line selectors. `artifact://` recovers full output a previous bash/eval/tool result spilled or truncated. `history://` is an agent's transcript as concise markdown; bare `history://` lists agents.
-- You MUST use `read` for every file, directory, archive, and URL inspection. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, `wget` are FORBIDDEN — any such bash call is a bug, regardless of how short or convenient it looks.
-- You MUST prefer `read` over a browser/puppeteer tool for URL content; only reach for a browser when `read` cannot deliver reasonable content.
-- For line ranges, append the selector to `path` (`path="src/foo.ts:50-200"`, `path="src/foo.ts:50+150"`). NEVER substitute `sed -n`, `awk NR`, or `head`/`tail` pipelines.
-- Summary footer names ranges to re-read? Re-issue ONLY the ranges you need via the multi-range selector. NEVER guess what's inside `..` / `…` markers — they carry no content.
+- You MUST use `read` for every file, directory, archive, and URL inspection. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, `wget` are FORBIDDEN bash calls, however short or convenient.
+- Line ranges go in the selector (`path="src/foo.ts:50-200"`) — NEVER `sed -n`, `awk NR`, or `head`/`tail` pipelines.
+- Summary footer names elided ranges? Re-issue ONLY those ranges. NEVER guess `..`/`…` content.