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 }
}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.
| Member | Signature | Contract |
|---|---|---|
| version | 1 | Protocol discriminator. Check this before issuing mutations. |
| getState | (): BuilderState | Returns 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): void | Replaces the document when the spec is valid; use applyOps for validation errors. |
| say | (text: string): void | Appends an agent-role reply to the transcript. |
| ask | (): ChatMessage | null | Returns the oldest user message that has no later agent reply. |
| exportCode | (): string | Generates a standalone Svelte source string from the current spec. |
| onUserMessage | (cb): () => void | Subscribes 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.
| op | Payload | Effect |
|---|---|---|
| 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.