What Needs a Human
Agents reach questions only a human can answer — a name to rule, a consent to give, a direction to pick. The requirement is one sentence: those questions are stored inside the chronicle, and a surface renders them. No terminal, no raw markdown file. An episode folder carries an attention.json; the board on /chronicle is the first thing the human meets, and the room now lets that human review context and answer in place.
One file, three doors, one answer transaction
pending loudly, and attention sync is the redemption path. A malformed existing store refuses the write — never write over a store you could not read.attention.json, field by field
{
"attention": 1,
"items": [
{
"id": "d1-consent-render",
"question": "Answer ep300's consent question — may the film render?",
"unlocks": "the eury render (issue draft ready in the vessel), and the
first true visual-music object",
"asked": "2026-08-15",
"state": "open",
"answer": null,
"answered_at": null,
"depth": [
"2026-07-27-episode-300-from-shadow-to-song/film/consent.md",
"ep332 vessel: DRAFT-mightyeagle-render-issue.md"
]
}
]
}The first store, transcribed from episode 332 — six open items on the day the loop shipped, one line wrapped here to fit the page.
| Field | Required | Is |
|---|---|---|
| id | required | Stable per-episode identity, e.g. d1-consent-render. |
| question | required | One line: what is asked of the human. |
| unlocks | optional | What answering it releases — why this one is worth a minute. |
| asked | optional | ISO date the question was raised. The board sorts newest-asked first. |
| state | required | "open" or "answered". Anything else reads as open. |
| answer | optional | The human's word, once given. |
| answered_at | optional | When it was given. |
| depth | optional | Where the full record lives — paths, owner/repo#n refs, chart ids. |
Reading is tolerant but never silent: an item missing id or question is dropped, and a store that cannot be parsed at all returns its parse error alongside an empty list — so an empty answer stays distinguishable from a failed look.
The board, and the room
Every open item across the chronicle, newest-asked first, above the explorer. Each row carries its question, what it unlocks, its episode, and links into that room. A quiet board renders nothing at all — an empty statement is still a true one.
Open items render first and stay actionable. Answered decisions live under History; when 0 remain open, the entire surface becomes one quiet completion row with the full decision record available on demand.
Existence is checked per folder before anything is read, so a 175-episode chronicle costs 175 stats rather than 175 reads.
The question can now be pulled, discussed, and answered
@miadi/attention-ui accepts items and an explicit answer or read-only interaction. It owns no route or credential, so another page can reuse the same open/history/context surface safely.
GET /api/chronicle/attention is public testimony. It reports answer capability so the room renders honestly. POST still requires loopback, an allowlisted TailScale human, or the writer token; a public reader sees no answer form.
chronicle_attention_list, _get, and _answer let an agent carry the same item through a discussion.
pnpm add @miadi/attention-ui
import { AttentionPanel } from "@miadi/attention-ui"
import "@miadi/attention-ui/styles.css"
// A public host may render the same record without a write door:
<AttentionPanel items={items} interaction="read-only" />API, MCP, and passages attention answer converge on answerChronicleAttention. A caller supplies episode + item identity, never a filesystem path; malformed, already-answered, empty, concurrent, and episode-escaping writes refuse.
The badge that shows a disagreement
Two surfaces read two different sources: this app reads the file, forgewright reads the wheel. They can disagree, and the disagreement is exactly what a reader needs to know — so each item is annotated from the .mw-attention.json receipt and wears the weaker word on the surface the human is already looking at.
The receipt says registered AND verified, and the store is not newer than the sync. The two surfaces agree; no badge is drawn.
The wheel leg has not completed, or the store was written more than two seconds after the last sync — the projection may be stale, and the honest word is the weaker one until a sync proves otherwise.
No receipt at all, or no entry for this item. The wheel has never heard of it.
passages attention
npm i -g passages passages attention add --episode 332 \ --question "Which name does the composer workspace take?" \ --unlocks "the folder can be created and the env var pinned"
| Verb | Does |
|---|---|
add --episode <ref> --question <text> [--unlocks <text>] [--id <id>] [--depth <ref>]… | Raise an item (state: open) and project it to the wheel. |
answer --episode <ref> --id <id> --answer <text> | Answer an item and project the new state. |
list [--episode <ref>] [--open] | Read items — every episode when no --episode is given. |
sync (--episode <ref> | --all) | Re-project file state onto the wheel; redeem what is pending. |
The wheel is read from MW_API_URL_OVERRIDE → MW_API_URL → MIADI_CHRONICLE_MW_URL, in that order; the vessel root from MIADI_CHRONICLE_ROOT. Without a wheel URL the file still writes and the receipt says pending — exit 3, loudly.
One contract, five owners
Every surface types against the same module, so the shape cannot fork into drifting copies. The dependency edge stays one-directional: the app and passages call inquiry-weave, inquiry-weave imports the schema, the reusable UI accepts structural data + callbacks, and the schema imports nobody.