| name | colloquy |
| description | Presents a concept, design, decisions, findings, or a run of work in progress as an HTML page the user comments on in the browser — the agent watches for comments, replies in-thread, and ships revised versions as the work moves. Use instead of a wall-of-text plan or a handed-over .md report. Triggers: "explain this in HTML", "write it up as a page", "write up the findings", "I want to see the options", a run of work whose items the user will want to watch go by, or an intricate design that needs review. |
| allowed-tools | ["Bash(colloquy:*)"] |
Present a concept, decision, findings, or work in progress as an HTML page the user opens
in a browser and works in place — a colloquy: they select text and comment, decide and
edit through what the page offers, you reply in-thread and ship revised versions, and a
banner shows whether you're working or waiting on them. Reach for it instead of a
wall-of-text plan or a .md report handed over by path, when a complex change needs
shared understanding or a decision before code, when a diagnosis or review is itself the
deliverable, or when a run of work has items the user will want to watch go by. With
nothing named below, the subject is whatever the session is about — the plan you were
about to give, the design under discussion, the findings you just gathered, or the work
you are about to start.
$ARGUMENTS
Soul
A page is the highest-bandwidth thing you can hand someone, so use it. Two habits carry
most of that.
Shape follows the subject. Ask what the subject is before writing about it. A set
of things renders as things — cq-milestones for work with stages, cq-board for work
the user re-orders, cq-options for a decision, cq-metrics for what was measured —
and the prose says what only prose can. Five paragraphs about five items hand the reader
the job of rebuilding the list you dissolved; the same five as items, each carrying its
own state, are read at a glance and commented on one at a time.
A page that asks leaves somewhere to answer. Anything you want a decision on ends in
a cq-options … choose, wherever on the page the question falls. Give each alternative a
<strong> title and its case — a sentence, or paragraphs with a <dl class="facts">
rail — and the options stack as full-width cards, so an argued decision lives inside the
group rather than in a table beside it. Where an alternative is a section of the page
itself — a diagram, a diff, something no card should hold — let the group be bare labels
naming those sections (for="<section id>"), which renders as a compact list; multiple
where more than one can win. Every such group carries a box for words, so "none of these" and a pick's
why need no separate gesture. A page presenting five candidates in prose and offering
nothing to press has handed the reader a document where it meant to ask a question.
The page keeps up with the work. A page is not only a thing to approve before the
work starts. Where the work is yours to do and the page tracks it, publish a version each
time the state moves — an item to active, then done; a finding added as you find it —
and the user watches it happen instead of reading about it afterwards. Their browser
follows each new version by itself, deferring only while they are mid-comment or
mid-drag, so a version costs them nothing. Ship one when an item's state actually
changes rather than at every step it took, and let
colloquy status <page> working "<detail>" carry the finer grain in between. Keep
colloquy wait <page> running while you work, in the host-specific loop below: a comment
that lands mid-flight ("skip that one") then reaches you at the next step rather than at
the end, and the banner reads as working throughout.
Where other sessions do the work — workers reporting to a page one orchestrator
publishes — they move it with colloquy report <page> <widget> <verb> name=value…
instead of a version: a declared state change (a cq-task's status, per the widget's
x-report entry) that the page paints live as provisional news, marked as a report until
a version answers it. It wakes the page's watcher like a user event, and the next
version adjudicates it: write the reported state and publishing absorbs the report by
id; keep your own state by marking the element overruled (why in the note); leave the
markup unchanged and the report keeps painting. version check refuses a version that
contradicts a standing report it never names — page catalog's $report has the rest.
Setup
The page lives in its own directory, conventionally
~/.local/state/colloquy/pages/<slug>/, where <slug> is a short kebab-case name
for the topic (migration-options, auth-diagnosis) — every leaf command takes the
page directory explicitly, so any location works. The directory survives the session
and is where every version, the event log, and the vendored widget layer live. It is
live state, not an archive: content with a life beyond the page leaves through
version export or a copied version, to wherever that content belongs.
The launcher is ${CLAUDE_SKILL_DIR}/../../bin/colloquy. Resolve
${CLAUDE_SKILL_DIR} to this skill's directory and use that launcher for every command
shown as colloquy below. Claude Code also puts the same launcher on PATH.
colloquy page init <page>
colloquy page catalog <page>
colloquy page media <page> <file>…
colloquy version check <page> --render
colloquy version publish <page> --version 1 --text "<changelog>"
colloquy version export <page> -o <file>
colloquy server run <page> [--host NAME]
colloquy status <page> working "<detail>"
colloquy report <page> <widget> <verb> name=value…
colloquy wait <page>
colloquy ack <page> <seq>
colloquy comment <page> --quote "<passage>" --text "…"
colloquy reply <page> --to <id> --text "…"
colloquy events <page>
colloquy transcript <page>
If the resolved launcher does not exist, the plugin payload is incomplete; say so. In a
repository checkout it lives at plugins/colloquy/bin/colloquy.
- Run
page init <page>, then read page catalog <page>. It prints the vendored
registry (widget schemas with examples) and the theme's class idioms, which vary per
project.
- Write the page as
<page>/versions/v1.html (conventions below).
- Start
server run <page> as a long-running background command: a background task in
Claude Code, or a unified-exec session in Codex. Hand over the URL it prints exactly
as printed: the key in it is what opens the page. Address, key and port are all stable
per directory, so the URL survives a restart.
- Run
version publish <page> --version 1 --text "<changelog>". Publishing checks
the version first and refuses a failure, so a half-written or broken file is never
live in the user's browser. Before the URL first goes out, run the browser gate
too: version check <page> --render (see "Before the URL goes out"). Then hand the
user the URL with a one-line orientation (select text to comment; on a sign-off
page, "✓ Looks good" approves; otherwise "End colloquy" ends comments without
approval) and enter the loop.
When the deliverable is the file
--export in the argument asks for the file rather than the live page: steps 1, 2 and 4
as above, then version export <page> -o <file> and hand back the file:// URL. No
server run, no colloquy wait, no loop — the page directory is still built, so the
same page can be served later without being rewritten, and the Stop hook covers only
pages that were served or waited on, so it has nothing to say about this one. Write the
file wherever the project puts things for the user to open.
While a page is live the same command answers "give me a copy": version export writes
any published version, as many times as asked, and the page carries on around it. The
copy is the page as the browser drew it, with the user's decisions replayed onto it
and the comment layer left behind.
Page conventions
-
Pages are complete HTML documents. version check enforces the scaffold — exactly one
stylesheet link (/theme.css) and one external script (the /colloquy.js module);
the rest of the head (title, charset, the cq-* metas below) is yours:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>…</title>
<link rel="stylesheet" href="/theme.css">
</head>
<body>
<main>
…authored HTML and widgets…
</main>
<script type="module" src="/colloquy.js"></script>
</body>
</html>
-
The theme owns the look. Palette, type, spacing, headings, tables, code,
details, and the class idioms all come from the vendored theme.css — write plain
semantic HTML and it gets the voice for free. A page-local <style> is the escape
hatch for genuinely page-specific presentation, not for re-declaring the palette.
-
Widgets are cq-* elements, validated against the vendored registry: attributes
carry scalars (enums, flags), children carry prose, an item's title is a leading
<strong> child. Every cq-* element takes an explicit end tag — <cq-diagram id="flow"/> is
rejected because HTML ignores the slash. A data-bodied widget (cq-diagram) holds
its notation in a <pre>, with < and > escaped — the whitespace is load-bearing,
and <pre> is the only thing in HTML that says so to a tool with no stylesheet to
read. The catalog is the authority; don't invent tags or attributes.
-
Give every section, major block, and widget item a stable, meaningful id: comments
anchor to the nearest id, and an anchor survives into a new version only where its
id does. The reader's place on the page falls back to those same ids when the text
around it was rewritten. Keep ids stable across versions so neither detaches, and out
of the cq- prefix, which the runtime coins its own ARIA targets in.
-
Edits to already-seen content ship as suggestions. Changing a passage the reader
has already seen — a rewrite, a deletion, above all the fix a comment asked for —
goes in a cq-suggestion: cq-old carries the current markup verbatim (its ids
ride there), cq-new the proposal, and the user accepts or rejects it in the
margin. Fresh content — the first version, a new section, a restructure — is
written straight, and comments cover it as usual. Name the answered thread
with resolves="<comment id>" so accepting the fix closes the thread too.
Deciding isn't the only answer: the proposed words are ordinary page text, so the
user can select them and comment instead — worth saying where the page
introduces its first suggestion, since ✓ and ✗ are the only visible affordances.
-
Who writes the words picks the shape. Three things change text once the page is
in front of the user, and they differ by seat rather than by style. Prose you own, rewritten
after the reader has seen it: a cq-suggestion, theirs to accept or reject. A
passage that is theirs to word — a release note, a summary in their voice: a
cq-draft, which nobody decides and the next version carries verbatim. Their
wording for prose you own: a suggestion comment, which reaches the log for you to
take or answer. So a draft never sits inside a suggestion — its words aren't yours
to propose — and a suggestion carries markup, not a widget's own state: proposing a
card's column or an option's pick has no form yet.
-
The runtime injects the status banner, comment sidebar, version picker, keyboard
shortcuts (? in the browser shows the reference), and a left panel listing the
machine's live colloquys with what each is doing or waiting for; don't build page UI
for any of those. It also collects what the page is still waiting on the reader for —
an undecided suggestion, a choose group with no pick, a task at review or blocked
— into a banner count they can step through with a, from the vocabulary's own
declarations (x-awaits). So write the asks as widgets and let the count find them;
a hand-written "still open" list beside them is a second copy that goes stale the
moment one is answered.
-
Sign-off is declared, not assumed. A page that asks for the user's assent — a
plan, a design, a proposed change, anything where approval unblocks work — declares
<meta name="cq-review" content="sign-off"> in the head, and the
banner offers "✓ Looks good". A page that only informs (a status report, an
incident chronicle) omits it: it takes comments only, and the banner instead
offers a neutral "End colloquy" control with no approval meaning.
version check rejects unknown cq-* metas and any other cq-review value.
-
Announce interactivity in prose. Someone new to the page won't guess from a grip glyph
or a hover cursor that a board takes drags or an options group takes clicks — the
sentence introducing the widget says it ("drag cards to reprioritize; your edits
reach me directly", "click an option to decide"). The widgets stay chrome-free on
purpose; the page's own words carry the affordance.
-
Never lose user text. A central tenet of the comment layer: drafts (the general
box, each reply, the selection composer) survive navigation, reload, version switches,
and server death; only a successful send clears them.
-
Diagrams are graphical, never ASCII. Flow, sequence, and state diagrams go in a
cq-diagram (mermaid source body); reach for hand-drawn inline <svg> only where
layout must be bespoke, drawn from the theme's tokens, with labelled nodes and
arrowheaded edges. Never box-drawing (┌─┐ │ ▼) in a <pre>.
-
Name a code block's language and it gets colored. Two shapes, by what the block
is for: <pre><code class="language-python"> for a literal the user selects and
quotes — a command, a config, a snippet of output — and <cq-code language="python"> for
a walkthrough, which adds line numbers, hi ranges, and cq-note remarks anchored at
a line. The language names are the same set either way, page catalog lists them, and
version check refuses one outside it. Nothing is inferred from the text, so a block
whose body isn't source — a transcript, a stack trace, a log — simply says nothing
and stays plain. A cq-diff needs no language and takes none: a unified diff spans
files, so each file's own path says what it holds, and a path naming nothing leaves
that file plain like any undeclared block.
-
Make references clickable. Write source locations as ordinary semantic links,
such as <a href="https://host/repo/blob/main/path/to/file.py#L88"><code>path/to/file.py:88</code></a>.
Render ticket keys, MR/PR numbers, and URLs as real <a> links, not plain text.
Inside a <cq-specimen> a fictional URL is fine.
-
Keep wide content inside the column — 720px in the default theme. The comment
layer anchors to on-screen text, so a page that scrolls sideways is hard to comment
on.
Give any element that can overflow (a <pre>, a <table>, an <svg>)
max-width: 100% or overflow-x: auto, and size diagrams responsively rather than a
fixed pixel width wider than the column. version check flags fixed widths that
exceed it.
-
Images come in by reference, never inline.
colloquy page media <page> <file>… copies files into the page directory and prints
the src to write; that path is the only form an image takes on a page, because a
base64 data: URI is more bytes than you can usefully type and it would sit in every
version forever. Each file is named by the hash of its bytes, so two versions showing
one screenshot share one copy and a version the user approved cannot come to show
them something else. version check refuses a /media/ reference the directory
can't answer.
Where the deliverable is a change to a UI with a real before state, let the reader
compare the renders rather than describing what moved: a cq-shot holds the pair and
flips between them in place. Capture both states at the same viewport (the
/playwright-cli:playwright-cli skill drives the browser; render the base commit in
a second worktree rather than stashing). Say in prose what changed — a downscaled
full-page shot shows that something moved and not what, and the column is 720px, so
crop to the part that moved wherever the change is smaller than the page.
-
Show real content as evidence; quote invented content in a specimen. Prefer
putting the actual file contents, diff, or output behind <details> over
paraphrasing it. An example that merely exhibits syntax or a widget goes in a
<cq-specimen> — its gutter and label mark the region as quoted rather than
spoken, and interactive widgets inside take no input — with visibly fictional
content: real project content in an example gets read as a live proposal.
-
Show the destination, not the journey. Explain the concept as it stands — total
cut-over. Don't spend content on what was considered before or how you got here.
Keeping the page current
A page shows where its topic stands now, with what came before still on it. That is
"Show the destination, not the journey" over time: the journey grows as the work does, so
v1's destination — four options laid out for a decision — is the journey by v4, once the
decision has been made and applied. Leaving it at full height in the order it was written
turns the page into the record of the investigation, and the user has to work the
present out of that.
Each version is therefore a rewrite toward the present. The body carries what is live —
the question in front of the user and what they need to answer it — and the lede says
what the page is asking now. A section the topic has moved past goes to a Settled
section at the foot: <h2>Settled</h2>, and under it one <details> per retirement, its
<summary> naming the question and what closed it (the option picked, or the section that
superseded it). Nothing is deleted. What retires moves intact, ids and all, so the anchors
hold and version check passes, and a user who wants the argument behind a settled
question opens it and finds what they read before.
A cq-options group has the same move built in. settled collapses it to one line naming
the pick, with every option behind a disclosure; the user can open it, disagree, and
pick again. Reach for it where a decision retires inside a section that stays live; a
section retiring whole takes its groups with it, marked the same way.
Retiring is not revising. The words don't change, so it is neither a cq-suggestion nor
grounds for restated — relocating a group the user picked in is a version agreeing
with them.
Time it by what is still moving rather than by what is finished. A decision stays live
while you are applying it and settles once nothing is revisiting it, usually a version or
two on, and a section the user is still commenting in stays in the body until that
thread closes.
The loop
Whenever you hand over the URL or finish a round of work, run
colloquy status <page> waiting "<what you want back>", then enter the loop for the
current host:
Every handover message carries the page's URL again, so the user can open the page
from the turn in front of them.
- Claude Code: start
colloquy wait <page> as a background task and end the turn.
Its completion returns as host input: an idle session starts a turn, while a working
session receives it between tool calls. Restart the background wait after each batch.
- Codex: send the URL to the user in an intermediate update before waiting. Start
colloquy wait <page> in unified exec, retain the returned session id, and keep the
current turn active. Where the user owns the next move, poll that exact session
with empty write_stdin calls and long yields until it returns. Where you are working,
leave the same waiter running, continue the work, and poll it between tool calls or
milestones so a comment can change the next decision. Never detach the wait and never
end the turn expecting its completion to start another one: Codex has no unprompted
completion delivery. Start a fresh wait session after each batch and retain its new id.
While colloquy wait runs, the banner reads " awaits" and puts the waiting
detail after it — the page's own line about what it needs from the reader. Write the
thing you want back, in one short clause ("pick a storage engine", "check the two
failure modes against what you saw"), rather than restating that you are waiting; the
line shares a row with the page's controls and ellipsizes when they need the room. A
page that asks nothing declares no detail, and the banner offers "select text to
comment" instead. The same clause is what a reader sees against this page's name in
every other colloquy's panel, which is where they pick which of several pages to
come to — so name the ask, not the page's subject, which the title beside it already
gives them.
The wait can stay open as long as the user takes, and exits when they comment, reply,
resolve, approve the page or end the colloquy, or edit an interactive widget (a drag on
a cq-board arrives as an action event) — or when a worker session posts a
colloquy report, which joins the same batch — printing the unacknowledged events
as JSON lines. Printing is deliberately not receipt: a
detached process can finish without its output ever entering model context. As soon as
a complete wait result enters context, run colloquy ack <page> <highest-seq> before
interpreting or handling it. If the wait output was truncated at all, acknowledge
nothing: run a new wait with enough output capacity to receive the whole batch. A scalar
cursor cannot represent a missing line in the middle. Acknowledgement is monotonic and
idempotent; an event posted between wait and ack has a higher sequence and remains
pending. Until ack, the next wait prints the batch again. Reading the full log with
colloquy events does not acknowledge it. User comments exist only through the browser;
colloquy comment posts as you, never as them.
A wait result while the page already says working leaves that status untouched;
handoff dates only a pickup from a non-working state.
For each acknowledged batch:
- Run
colloquy status <page> working "<what you're doing>" and refresh the detail at
each milestone. The banner shows it live, and reads a state left unrefreshed long
enough as the agent having gone quiet.
- Address every event
colloquy wait printed. Each is JSON carrying the server-minted
id that colloquy reply --to takes:
- A comment:
colloquy reply in-thread, and change the page where the comment
warrants it — usually both. A reply's --text is brief Markdown — lists, code,
fenced blocks, a table, bare URLs arrive as links — and every raw tag in it
renders as its characters: write <T>, <div>, or a cq- tag in prose and
the user reads exactly those words. Point at the page with an ordinary
Markdown fragment link — "pick the channel decision" — and the
words carry the reader to that element, opening whatever tab or settled group
hides it. Reach for one whenever a reply names a part of the page: telling
them where to look costs a sentence they then have to act on, where a
reference is the act. Nothing checks the id, so keep it right; one this
version hasn't got renders detached, like a quote whose passage left the page.
To put a widget in the thread (a small
cq-diagram explaining a fix renders live there), pass its markup as --markup,
which renders after the text. colloquy reply validates it against the vendored
registry and rejects what version check would, and a widget's ids must be
fresh — it refuses ids the page or an earlier message already uses, and
version check keeps later versions off a reply's.
- A suggestion (a comment with
"suggestion": true) proposes replacement text
for its quoted passage: take it verbatim into the next version, or reply with
why not — never silently rewrite it.
- A page-widget action is the user editing the document through a widget — a
board drag arrives as
{"kind": "action", "widget": "feeder-board", "action": "move", "detail": {"card": "card-baffle", "to": "col-doing", "index": 0}}, an
options pick with "action": "choose" and "detail": {"options": ["st-s3"]}
(every option that now holds the pick, so an empty list is one cleared),
a suggestion decided with "action": "accept" or "reject" — and they have
already seen the change on screen. It stays on screen without your help: the
page replays every recorded action onto every later version, so their edit
survives a republish whether or not your markup mentions it. Write the next