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;
};
MemberContract
versionLiteral protocol version 1
getStateRead the current spec, selected id, transcript, pending user messages, and bounded activity history
blocksRead serialisable block metadata without transferring Svelte components
applyOpsApply a batch and receive an explicit success flag plus all reported errors
reportActivitySubmit one runtime-validated validation, operation, or agent activity
setSpecReplace the current builder specification
sayAppend an agent chat message
askReturn the oldest unanswered user message, or null
exportCodeProject the current specification to Svelte source
onUserMessageSubscribe 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 request entry and a queued reply entry; an agent reply resolves the waiting request;
  • applyOps records 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 persistence state.

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:

  • kind must be validation, operation, or agent;
  • status must be queued, active, done, or error;
  • label must be non-blank and no longer than 120 characters;
  • optional detail must be no longer than 500 characters; and
  • unknown fields are rejected. An input such as chainOfThought therefore 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:

  1. relay subscribers registered with onUserMessage receive its ChatMessage; and
  2. document dispatches a bits-builder:user-message CustomEvent whose detail is that new ChatMessage.

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:

  1. Wait for document.documentElement.dataset.bitsBuilder === 'ready'.
  2. Subscribe with onUserMessage, or listen for bits-builder:user-message and then call ask.
  3. Read getState() and blocks() before constructing operations.
  4. Translate the user request into the smallest ordered BuilderOp[] batch.
  5. If useful, call reportActivity(...) with a concise observable validation, operation, or agent status—never hidden reasoning.
  6. Call applyOps(ops).
  7. If ok is 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.
  8. Call say(...) with a concise, accurate outcome. The relay method accepts text only; it does not accept an ops argument.
  9. Re-read state before a dependent second edit rather than assuming an id or tree position.
  10. 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 chainOfThought activity 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.

loca.zone · bits.loca.zone · Svelte