Agent Relay Protocol
src/lib/builder/relay.ts implements one versioned browser surface at window.bitsBuilder, installed by the builder route. It is for an OMP agent operating through the browser relay, not a server-side LLM endpoint. The activity additions are backward-compatible: the protocol remains version 1, and every pre-existing method and signal retains its contract.
Version 1 API
type BuilderActivityStatus = 'queued' | 'active' | 'done' | 'error';
type BuilderActivityKind =
| 'request'
| 'validation'
| 'operation'
| 'persistence'
| 'agent';
type BuilderActivity = {
id: string;
kind: BuilderActivityKind;
status: BuilderActivityStatus;
label: string;
detail?: string;
at: number; // epoch milliseconds
};
type BuilderActivityInput = {
kind: 'validation' | 'operation' | 'agent';
status: BuilderActivityStatus;
label: string;
detail?: string;
};
type BuilderState = {
version: 1;
spec: BuilderSpec;
selectedId: NodeId | null;
messages: ChatMessage[];
pending: ChatMessage[];
activities: BuilderActivity[];
};
type BitsBuilderRelay = {
version: 1;
getState(): BuilderState;
blocks(): BlockMeta[];
applyOps(ops: BuilderOp[]): { ok: boolean; errors: string[] };
reportActivity(activity: BuilderActivityInput): { ok: boolean; errors: string[] };
setSpec(spec: BuilderSpec): void;
say(text: string): void;
ask(): ChatMessage | null;
exportCode(): string;
onUserMessage(cb: (m: ChatMessage) => void): () => void;
};| Member | Contract |
|---|---|
version | Literal protocol version 1 |
getState | Read the current spec, selected id, transcript, pending user messages, and bounded activity history |
blocks | Read serialisable block metadata without transferring Svelte components |
applyOps | Apply a batch and receive an explicit success flag plus all reported errors |
reportActivity | Submit one runtime-validated validation, operation, or agent activity |
setSpec | Replace the current builder specification |
say | Append an agent chat message |
ask | Return the oldest unanswered user message, or null |
exportCode | Project the current specification to Svelte source |
onUserMessage | Subscribe to user messages; its return value unsubscribes |
The relay surface therefore has ten public members: the literal version plus nine callable methods. Existing version 1 consumers can ignore the additive activities state field and reportActivity method.
Observable activity contract
Activity is a concise account of observable actions, statuses, and results. It must never contain chain-of-thought, hidden reasoning, private deliberation, or an agent’s internal scratch work.
The builder records its own activity where it owns the event:
- a user message creates a completed
requestentry and a queued reply entry; an agent reply resolves the waiting request; applyOpsrecords validation, each ordered operation, and the batch result, including structured failures;- undo and redo record their observable operation result; and
- session restore and save record
persistencestate.
request and persistence are automatic sources and cannot be supplied through reportActivity. Relay agents may report only validation, operation, or agent activity. The input is a strict runtime-validated object:
kindmust bevalidation,operation, oragent;statusmust bequeued,active,done, orerror;labelmust be non-blank and no longer than 120 characters;- optional
detailmust be no longer than 500 characters; and - unknown fields are rejected. An input such as
chainOfThoughttherefore returns{ ok: false, errors: [...] }rather than entering the activity stream.
Successful reports are trimmed, assigned their store-generated id and epoch-millisecond at, appended once, and return { ok: true, errors: [] }. Invalid reports append nothing and return every validation error.
Activity retains the newest 120 entries. The browser persists that bounded list client-side under bits-builder:activity:v1, restores only valid entries, and continues in memory if local storage is unavailable. Activity is deliberately absent from the server session payload, which remains exactly { spec, messages }.
Readiness and signals
installRelay(store) installs the API and returns a teardown function. While installed, the root <html> element carries:
data-bits-builder="ready"Every new user message has two browser-visible signals:
- relay subscribers registered with
onUserMessagereceive itsChatMessage; and documentdispatches abits-builder:user-messageCustomEventwhosedetailis that newChatMessage.
An agent may consume the typed message from event.detail, onUserMessage, ask, or getState. The event and subscription are push signals; ask remains the ordered query for the oldest unanswered user message.
Agent-relay loop
flowchart LR User["User"] -->|submits request| UI["Builder UI"] UI -->|records user message| Relay["window.bitsBuilder"] Relay -->|event and subscription callback| Agent["OMP browser-relay agent"] Agent -->|ask, getState, blocks| Relay Agent -->|applyOps, reportActivity| Relay Relay -->|reactive store update| UI Agent -->|say| Relay UI -->|preview and transcript| User
A reliable agent loop is:
- Wait for
document.documentElement.dataset.bitsBuilder === 'ready'. - Subscribe with
onUserMessage, or listen forbits-builder:user-messageand then callask. - Read
getState()andblocks()before constructing operations. - Translate the user request into the smallest ordered
BuilderOp[]batch. - If useful, call
reportActivity(...)with a concise observable validation, operation, or agent status—never hidden reasoning. - Call
applyOps(ops). - If
okis false, inspect every error and re-read state before reporting the outcome. A batch is ordered but not transactional, so valid earlier operations may already have applied. - Call
say(...)with a concise, accurate outcome. The relay method accepts text only; it does not accept anopsargument. - Re-read state before a dependent second edit rather than assuming an id or tree position.
- Invoke the unsubscribe function when the agent no longer needs message callbacks.
Direct replacement and export
setSpec is available for a complete versioned document supplied by the agent, but its void contract does not expose validation errors. Incremental work should prefer applyOps because its result reports errors and successful spec mutations enter the same history and persistence path as UI edits.
exportCode returns source derived from current state. It does not write a route or deploy an application; those would be separate, explicitly authorized actions.
Verification state
The detailed 2026-08-31 behavioral receipts in Operations were captured against an isolated local production build before release.
- The local relay exposed the complete version 1 surface, including
reportActivity, and returned 22 block records. - Valid and invalid operation batches, pending-message resolution, undo/redo, export, reload durability, and strict rejection of an unknown
chainOfThoughtactivity field were observed. - Activity survived reload through
bits-builder:activity:v1, while the server session remained{ spec, messages }. - After authorized deployment, the live builder exposed the same ten-member relay surface, returned 22 block records, and rendered the documented activity surface without horizontal overflow.