Agent interface

OMP browser relay

The OMP browser relay lets an agent operate a live Chromium tab. On the builder route, the page installs a small versioned API on window.bitsBuilder. The agent reads the same block metadata and state a person sees, submits validated operations, and leaves replies in the shared transcript.

Open a dedicated builder tab before attaching the relay. Relay actions occur in the browser's real session and are persisted by the builder, so inspect state before mutating it.

Connect

Open the builder in an OMP browser session with relay mode enabled. The payload below is the browser open request.

{
  "action": "open",
  "name": "bits-builder",
  "url": "https://bits.loca.zone/builder",
  "app": { "relay": true }
}
Do not call the API until html[data-bits-builder="ready"] exists. The marker is installed with the relay API and removed by its teardown, so it is the authoritative readiness signal rather than page load alone.

window.bitsBuilder API

Protocol version 1. Every member is synchronous.

version: 1
MemberSignatureContract
version1Protocol discriminator. Check this before issuing mutations.
getState(): BuilderStateReturns the current spec, selection, transcript and pending user messages.
blocks(): BlockMeta[]Returns the serialisable block catalog, including defaults and editable fields.
applyOps(ops: BuilderOp[]): { ok, errors }Validates and applies an ordered mutation batch.
setSpec(spec: BuilderSpec): voidReplaces the document when the spec is valid; use applyOps for validation errors.
say(text: string): voidAppends an agent-role reply to the transcript.
ask(): ChatMessage | nullReturns the oldest user message that has no later agent reply.
exportCode(): stringGenerates a standalone Svelte source string from the current spec.
onUserMessage(cb): () => voidSubscribes to new user messages and returns the unsubscribe function.
type BitsBuilderRelay = {
  version: 1;
  getState(): BuilderState;
  blocks(): BlockMeta[];
  applyOps(ops: BuilderOp[]): { ok: boolean; errors: string[] };
  setSpec(spec: BuilderSpec): void;
  say(text: string): void;
  ask(): ChatMessage | null;
  exportCode(): string;
  onUserMessage(cb: (message: ChatMessage) => void): () => void;
};

declare global {
  interface Window { bitsBuilder: BitsBuilderRelay }
}

BuilderOp vocabulary

Send ordered operations as one batch. Check ok before claiming success; errors contains validation failures. A batch is ordered but not transactional: valid earlier operations can apply before a later operation fails.

opPayloadEffect
set-spec{ spec }Replace the complete BuilderSpec.
set-title{ title }Set the document title without replacing its nodes.
add{ node, parentId?, index? }Insert a node at the root or inside a container; id is optional.
update{ id, props }Update the supplied props on one existing node.
remove{ id }Remove one node and its descendants.
move{ id, parentId?, index }Move a node to a root or container index.
select{ id }Select a node for the inspector; pass null to clear selection.
clear{}Remove every root node and clear selection.

Copyable operation batch

IDs supplied by an agent make later operations in the same batch easy to address.

[
  {
    "op": "set-title",
    "title": "Release readiness"
  },
  {
    "op": "add",
    "node": {
      "id": "release-stack",
      "type": "stack",
      "props": {
        "direction": "vertical",
        "gap": "4",
        "align": "stretch"
      },
      "children": []
    }
  },
  {
    "op": "add",
    "parentId": "release-stack",
    "node": {
      "id": "release-heading",
      "type": "heading",
      "props": {
        "text": "Relay-built section",
        "level": "h2",
        "align": "left"
      }
    }
  },
  {
    "op": "select",
    "id": "release-heading"
  }
]

User-message signal

Every new user-role message while the relay is installed dispatches a bits-builder:user-message CustomEvent on document. Its detail is the new ChatMessage. The relay subscription observes the same signal without requiring DOM event wiring.

document.addEventListener("bits-builder:user-message", (event) => {
  const message = event.detail; // ChatMessage
  console.log(message.id, message.text);
});

// The equivalent relay subscription returns an unsubscribe function.
const unsubscribe = window.bitsBuilder.onUserMessage((message) => {
  console.log(message.text);
});
unsubscribe();

Minimal agent loop

After the relay open request above, run this in the browser session. It waits for readiness, polls the oldest unanswered request, reads the block catalog, applies a batch, checks the result, and records an agent reply. Replace the deliberately small planner with the agent's own operation plan.

// Run inside an OMP browser `run` call after opening the relay tab.
await page.waitForSelector('html[data-bits-builder="ready"]');

const version = await page.evaluate(() => window.bitsBuilder.version);
if (version !== 1) throw new Error('Unsupported bitsBuilder protocol');

function planBuilderOps(request, catalog) {
  // Replace this small deterministic planner with the agent's own plan.
  if (/clear|start over/i.test(request.text)) return [{ op: 'clear' }];
  const title = request.text.slice(0, 80);
  const heading = catalog.find((block) => block.type === 'heading');
  return [
    { op: 'set-title', title },
    ...(heading
      ? [{ op: 'add', node: {
          type: heading.type,
          props: { ...heading.defaults, text: title }
        } }]
      : [])
  ];
}

let request = null;
while (request === null) {
  request = await page.evaluate(() => window.bitsBuilder.ask());
  if (request === null) {
    await new Promise((resolve) => setTimeout(resolve, 750));
  }
}

const catalog = await page.evaluate(() => window.bitsBuilder.blocks());
const ops = planBuilderOps(request, catalog);
const result = await page.evaluate(
  (nextOps) => window.bitsBuilder.applyOps(nextOps),
  ops
);

await page.evaluate((outcome) => {
  window.bitsBuilder.say(
    outcome.ok
      ? 'Applied the requested changes.'
      : `I could not apply the changes: ${outcome.errors.join('; ')}`
  );
}, result);
// Repeat this run call for the next user message.