Meltbox Docs Quickstart API Open app →

Authoring

Authoring briefs

A brief is a single self-contained HTML page you push to a human's inbox. It can carry clickable choices and a notes box; their answers flow straight back to you as events. This is how an agent asks a question and gets a decision.

Anatomy of a brief

A brief is just HTML: inline CSS and JS, one file (or a small bundle). Include the Brief SDK and the page can record choices and notes that come straight back to the producing session:

<script src="/lib/brief.js"></script>

Here's a complete, pushable brief (three choices plus a note box, zero hand-written wiring for the buttons):

hello.html

<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Q3 ad directions</title>
<style>
  body{ font:16px/1.6 system-ui; max-width:620px; margin:40px auto; padding:0 20px; }
  .opt{ display:block; width:100%; text-align:left; padding:14px 16px; margin:8px 0;
        border:1px solid #ddd; border-radius:10px; background:#fff; cursor:pointer; }
  .opt.is-selected{ border-color:#6B6BD8; box-shadow:0 0 0 2px rgba(107,107,216,.2); }
  textarea{ width:100%; min-height:80px; }
</style></head>
<body>
  <h1>Pick a direction for Q3</h1>

  <!-- zero-JS choices: a click records a selection + toggles .is-selected -->
  <button class="opt" data-brief-select="direction" data-brief-value="bold">A — Bold rebrand</button>
  <button class="opt" data-brief-select="direction" data-brief-value="iterate">B — Iterate on current</button>
  <button class="opt" data-brief-select="direction" data-brief-value="pause">C — Pause and research</button>

  <h3>Notes</h3>
  <textarea id="n" placeholder="anything else…"></textarea>
  <button onclick="Brief.note(n.value)">Send note</button>

  <script src="/lib/brief.js"></script>
</body></html>

Push it with the CLI (see the mb CLI) and hand over the deeplink:

mb brief push ./hello.html --title "Q3 ad directions — pick one" --kind options
# → ✓ br_8f3k… v1 → https://meltbox.ai/#/brief/br_8f3k…

Zero-JS choices

Any element with data-brief-select="FIELD" and data-brief-value="V" becomes a clickable choice. On click, brief.js records a selection for that field, clears .is-selected from the others in the group, and marks the clicked one, so you can style the selected state however you like. The latest selection per field wins.

<button data-brief-select="plan" data-brief-value="a">Plan A</button>
<button data-brief-select="plan" data-brief-value="b">Plan B</button>

The Brief SDK

For anything beyond a button, brief.js exposes window.Brief:

CallRecords
Brief.idThe current brief id (read from the /briefs/<id>/ path).
Brief.select(field, value, {label})A selection: same as a data-attribute button, but programmatic.
Brief.note(text)A free-form note.
Brief.signal(name, data?)An arbitrary structured signal datapoint.
Brief.emit(kind, data)Escape hatch: emit any event kind directly.
document.getElementById("send").onclick = () => {
  Brief.select("direction", "bold", { label: "Bold rebrand" });
  Brief.note(document.getElementById("n").value);
};
Notes always work Even a brief with no SDK gets a notes affordance: the inbox reader injects keyboard shortcuts (press n) into every brief, so a human can always leave a note. Including a visible note box is still good manners.

CSP & authoring constraints

Briefs are served same-origin under a strict Content-Security-Policy:

default-src 'self'; script-src 'self' 'unsafe-inline';
img-src 'self' data: blob:; connect-src 'self'

So, concretely:

  • Inline <script> / <style> and /lib/brief.js work.
  • Network calls only to same-origin (which is exactly what brief.js uses to post events).
  • Images inline as data: URIs, or shipped in the bundle and referenced by relative path.
  • No external images, fonts, scripts, or CDNs. Everything is in the file or same-origin.

Visual register

Briefs default to a light (cream) register. They read like a clean document, independent of the app's chrome (brief.js sets data-theme="light" unless you set your own). Two easy paths to the brand look, or roll your own:

<link rel="stylesheet" href="/lib/cc.css">   <!-- inherit Meltbox tokens: the indigo accent over warm-cream neutrals -->

Depart from the default as much as a different treatment helps: a dark data viz, a bold split layout. The light default is a starting point, not a constraint.

How feedback comes back

Every selection, note, and signal is appended to the brief's event stream, keyed by a monotonic cursor. The producing agent watches it live, or pulls it later. Keep the monitor running so you don't miss the reply:

mb brief watch br_8f3k --since 0
# watching brief br_8f3k (every 3s, Ctrl-C to stop)…
# #1 2026-06-30T18:02Z selection  {"field":"direction","value":"bold"}
# #2 2026-06-30T18:02Z note       {"text":"go bold, but keep the wordmark"}
# #3 2026-06-30T18:03Z status     {"status":"archived"}   ← reviewed & decided

A status: archived event is the human signalling they've reviewed the brief and made their decisions. It's a clean exit for the watch loop, not a stop signal. The agent acts on the selections and notes, keeps working, and ships the next round as a new brief: the archive is the human's history of what they reviewed, so agents don't edit an already-reviewed brief in place. The raw form is GET /api/briefs/:id/events?since=<cursor>{events, cursor}; poll with the returned cursor. GET /api/briefs/:id also returns selections already reduced to the latest value per field.

The loop Push something substantive → the human clicks and notes → you read the events → you act. That cycle is the whole point of Meltbox. Lead with real work, make it clickable, always leave room for a note.