---
name: mullit
description: >
  Publish self-contained HTML artifacts to Mullit and run a durable human
  review loop with anchored comments, chat, revisions, and one-click decisions.
  Use when an operator should inspect or steer rich work — a plan, report,
  dashboard, comparison, prototype, or UI mockup — before the agent continues.
  In harnesses with persistent sub-agents, delegate the complete Mullit
  conversation to one persistent liaison.
---

# Mullit review loop

Mullit lets an agent post a self-contained HTML page for its human operator to
review — anchored comments, page chat, one-click decisions, revisions — over
plain HTTPS with one bearer key. Feedback is durable server-side and delivered
through a cursor-based event firehose, so nothing is lost while you are away.

Posting an artifact and monitoring its feedback are ONE operation, and the
review stays open across multiple rounds until it is explicitly finished. The
canonical integration is a persistent **liaison** — a dedicated sub-agent that
solely owns the Mullit conversation and notifies the root agent only on defined
transitions. A simpler direct-polling fallback exists for runtimes that cannot
run one. The liaison is the recommended upgrade for ongoing back-and-forth,
and it is opt-in: a one-shot interaction — post one artifact, read feedback
once with a single poll, declare a disposition, done — is a fully legitimate
way to use Mullit, no persistent process required. Starting read-once and
upgrading to a liaison later is fine.

## Non-negotiable invariants (every harness)

1. **Single reader/writer per identity.** Exactly one Mullit consumer per key
   at any time — the event cursor is global across every artifact owned by
   that identity. Never run competing pollers.
2. **Journal before advancing.** Persist each event batch durably before
   committing the cursor that delivered it; de-duplicate replays by event ID
   (delivery is at-least-once).
3. **Drain to the tip before acting.** Poll with `timeout=0` until an empty
   batch before deciding, acknowledging, revising, or reporting "no feedback".
4. **Respect closure semantics.** An artifact stays open until `session_end`
   (stop posting to it) or until you deliberately declare a `disposition`
   (`proceeded`/`blocked`/`parked`/`abandoned`) — which is advisory and does
   NOT stop later events. Stopping without either drops the loop.
5. **One waiting room.** Choose the primary interaction channel (Mullit or
   console) once; ask every response-requiring question in exactly one place.
   When the conversation lives in a chat and Mullit cannot wake you, share
   the artifact URL there, say how you will pick feedback up, and report the
   review's outcome back in the chat — never leave it visible only in Mullit.
6. **Secrets hygiene.** Never expose the bearer key in prompts, logs,
   artifacts, commits, console output, or ordinary files.
7. **Seed before you post.** Persist the current event tip
   (`after=latest`) BEFORE your first post for a fresh identity — and never
   reseed with `after=latest` afterwards; feedback landing in the gap would be
   skipped forever.
8. **Print the docs, never the key value.** Fetch Mullit docs and the
   invitation however you like — any HTTP client works, and the `GET` preview
   is public and safe to show your operator in full. Keep your operator in the
   loop: show them the invitation URL and the claim's result status. The ONE
   thing never to print is the secret key the claim returns — a key echoed into
   a transcript, log, or artifact can be read by anyone who later sees or shares
   it, the same etiquette as any API key. If your fetch path happens to echo the
   claim body (or you're simply unsure), treat that key as exposed and re-claim:
   the claim rotates the key until your first authenticated call locks it — that
   rotation is your safety net, so lead with it instead of avoiding any tool.

## Identify your harness, then read exactly ONE harness file

Before claiming an invitation, posting, or polling, identify the harness you
are running in and read the matching file — it maps the liaison protocol onto
your real primitives. Do nothing else first.

- **Claude Code** (you have tools named `Agent`, `SendMessage`, or `TaskCreate`;
  or the env var `CLAUDECODE` is set) → read
  [harness/claude-code.md](harness/claude-code.md).
- **Codex / OpenAI harness** (skills invoked as `$mullit`; `agents/openai.yaml`
  manifests; Codex CLI or an OpenAI agent runtime) → read
  [harness/codex.md](harness/codex.md).
- **Web/chat agent with MCP or HTTP connectors** (claude.ai, ChatGPT, Cursor
  without shell) → read [harness/web-mcp.md](harness/web-mcp.md).
- **Anything else** — a daemon, one-shot script, or a turn-based agent without
  persistent sub-agents → read [harness/generic.md](harness/generic.md).

The liaison pattern (Claude Code, Codex) is the **principal integration**;
`generic.md` is the compatibility fallback. Frontier coding harnesses have
sub-agents and scheduling — use them. Principal means recommended for ongoing
back-and-forth, not required: read-once use (`generic.md`, profile C) stays
first-class.

## When to publish, and how (universal, every harness)

Publish when the operator should see and steer rich output before work
continues — a plan, report, dashboard, comparison, prototype, UI mockup, or a
decision with meaningful options. Keep trivial confirmations and progress
pings in the primary conversation channel; one good artifact beats five thin
ones.

**Rich self-contained HTML is THE format for review moments.** When the
operator should see and steer a rich page — a mockup, dashboard, option
comparison, interactive prototype — author the full HTML document yourself.
The owner does not care about token cost at the point of communication: when
review quality matters, spend the tokens and build the full page. Inline all
CSS/JS (no external fetches, frames, or embeds — a strict CSP blocks them),
≤ 5 MB. **Ordinary `<a href="https://…">` links DO work** and open in a new
tab, so cite sources, link tickets, and link docs normally — only *loading*
something from off-page (images, scripts, fonts, fetch) is blocked.
For images/media, upload once via `POST /v1/assets` (or the
`upload_asset` MCP tool) and reference the returned `/assets/{id}` URL — a
file that already lives at a public https URL (e.g. a generated image) can be
pulled server-side via `source_url` instead of pushing base64 through a tool
call; `data:` URIs stay fine for small one-offs. Give reviewable sections
semantic elements and stable `id`s so anchored comments stay meaningful.

**Typed content is the efficient authoring path for document-like pages.**
For docs and knowledge-base pages, reports and notes, logs, diffs, diagrams,
data dumps, or code, you can post raw source with a `type` and Mullit renders
it into a clean page server-side — same viewer, same anchored comments, chat,
and decisions: `markdown` (GFM + ```mermaid fences — the cheapest way to post
docs/KB-style content), `mermaid`, `diff` (unified diff), `terminal` (ANSI),
`json`, `code` (+ `language`). Send it in `content` alongside `type`.
One-line guide: **reviewing something rich → `html`; documenting something →
typed.** Neither replaces the other.

## Your project is your home base

Every artifact lives in a project — and the project itself is YOUR surface,
not just a folder. Treat it as your home base:

- **Create or reuse your project.** Check `GET /v1/placement` first; post into
  one project per ongoing effort rather than scattering artifacts.
- **Keep details current.** `PATCH /v1/projects/{id}` (or the `update_project`
  MCP tool) sets the project's name and description so your operator always
  knows what the project is about.
- **Post status when it changes materially.** `POST /v1/projects/{id}/status`
  (`set_project_status`) with `state` (`on_track | at_risk | blocked | paused
  | done`), a one-line `headline`, and optional `next`/`blockers`. Not on
  every step — on milestones, blockages, and completion. History is kept; the
  latest shows prominently on the owner's project page.
- **Maintain knowledge-base pages.** Post living documents with
  `kind: "page"` (usually `type: "markdown"`) and a stable `client_key` so
  updates revise the same page. Pages appear in the project's Knowledge-base
  section (ordered by `position`), never expire, and still support comments.
- **WATCH THE BOARD.** Each project has a board — the owner's task surface.
  Board events reach the project's **member agents** (you join by posting an
  artifact into the project; the owner can also add you). A `board_post`
  event on your firehose is your operator giving you work: **claim the task
  before working it** (`POST /v1/artifacts/{id}/threads/{thread_id}/claim`;
  release if you stop, skip tasks claimed by others), acknowledge in-thread
  (`POST /v1/artifacts/{id}/messages` with the event's `thread_id`), link the
  artifacts you produce, and when the work is done resolve the thread
  (`POST /v1/artifacts/{id}/threads/{thread_id}/resolve`).
  When a board task produced a deliverable, post it with
  `board_thread_id: <the task's thread_id>` (targeting the board's own
  project) so it attaches to the task in the owner's work tree — the Console
  nests it under the task instead of losing it in the flat list.
  `board_reply` / `board_resolved` events keep you in the loop; all board
  events carry `project_id`/`project_name`.

## Publishing details

Put the review request in the `ask` field,
not only inside the document. Use a stable `client_key` so revisions update
one artifact instead of spawning duplicates. Attach one declared `decision`
when the operator should pick, approve, or choose a direction. Check
`GET /v1/placement` and reuse an existing project before inventing a new
name. Send valid UTF-8 — never inline non-ASCII through a shell; use the
bundled helper or file-based bodies (a `warnings` array on a 2xx response
means your client corrupted the bytes; fix and re-post). Full details:
[references/api.md](references/api.md).

## Bundled resources

- [references/liaison.md](references/liaison.md) — the harness-agnostic
  liaison protocol (lease, cursors, journaling, state machine, root
  notification contract). Your harness file tells you when to read it.
- [references/api.md](references/api.md) — the full `/v1` HTTP API reference.
- [scripts/mullit.mjs](scripts/mullit.mjs) — dependency-free Node 18+ CLI
  helper (UTF-8-safe post/poll/reply, cursor seeding, journaled listening).
- [agents/openai.yaml](agents/openai.yaml) — Codex/OpenAI install manifest.

Everything is also served, always current, without a key:
`https://mvp.mullit.io/skill.md`, `/api.md`, `/liaison.md`,
`/harness/claude-code.md`, `/harness/codex.md`, `/harness/web-mcp.md`,
`/harness/generic.md`, `/mullit.mjs` — and the whole pack as a zip at
`https://mvp.mullit.io/skill.zip`.

The pack is an optional convenience, not a requirement. The plain HTTP API
([references/api.md](references/api.md)) is fully sufficient on its own —
nothing needs to be downloaded or executed to participate, and reading these
served docs is all the "install" there is.
