Skip to content

01 — Concepts

NOTE

Status: Draft (normative). This document defines the conceptual model and the terminology used by every other document. When other documents conflict with this one, this one wins and the conflict is a spec bug.

TIP

The whole model in five sentences. A component that wants to be agent-controllable registers capabilities: reads (observations), view changes (actions), or references to existing backend operations (procedure references) — everything else on the page is invisible to agents. Capabilities exist only while their component is mounted, are typed with JSON Schemas, and are addressed by stable, code-authored ids. Backend operations are never redefined in the frontend; they are only made contextually available, with inputs bound from UI state. Every invocation re-checks availability and policy at execution time, and anything dangerous requires single-use user confirmation. The server re-validates domain operations regardless of anything the frontend did.

Normative language and stability labels

  • MUST / MUST NOT / SHOULD / SHOULD NOT / MAY are used as in RFC 2119.
  • Every API and behavior in this spec carries one of three labels:
    • Draft — intended for v0.1; expected to stabilize with at most minor changes.
    • Experimental — may change incompatibly or be removed; opt-in.
    • Future — described for orientation; not part of the initial implementation.
  • Nothing is labeled Stable yet: v0.1 is implemented and published as 0.x, and no API earns Stable before it survives a release cycle unchanged (see the graduation criteria in 12-roadmap.md).

The two planes

Every operation an agent can perform through an application belongs to exactly one plane.

Domain plane (domain:)

Operations that have meaning independently of any mounted UI: fetch devices, rename a device, disable a device, generate a report, invite a user, export data, update persistent configuration.

  • They live in the backend, defined as oRPC procedures and exposed to agents via orpc-agent.
  • agent-surface MUST NOT define, reimplement, or duplicate them. It MAY reference them (see 05-orpc-integration.md) to make them contextually available and to bind inputs from UI state.
  • The server is the sole authority: it re-validates authentication, authorization, input, and policy on every call, regardless of anything the frontend did.

Presentation plane (view:)

Capabilities bound to the currently mounted view and its local state: navigate to a route, open/close a drawer, switch tab, change a view filter, select table rows, sort, show/hide columns, expand a chart, focus a map marker, read the visible state of a component.

  • They live in the frontend, registered by components through agent-surface.
  • They exist only while the owning component is mounted and enabled.
  • Their authority is the client runtime's policy pipeline — advisory by design, since the client is not a trusted boundary. They therefore MUST NOT be the only gate in front of anything irreversible or server-authoritative.

The litmus test

Does this operation make sense even when no user interface is open?

Yes → domain plane. No → presentation plane.

Consequence worth spelling out: changing a filter that causes the app to re-query the server is still a presentation action. The fetch is a reactive consequence mediated by the app's normal data layer — identical to what happens when the user clicks the same filter. The action's direct effect is local view state. Direct server effects are reserved for domain procedure references.

Capability

A capability is the unit of agent-visible functionality. There are exactly three kinds:

KindPlaneDirectionDeclared effect
Observationviewreadread (implicit)
Actionviewwritelocal-state | navigation
Procedure referencedomainwrite (or read)server-query | server-mutation | external-side-effect | destructive

Observations

Read the semantic state of a component: visible rows, selected rows, active filters, current sorting, active tab, current route, open drawer, visible time range, items shown on a map.

Observations:

  • MUST NOT produce side effects (normative contract on the author; the runtime cannot verify it, but the testing package helps enforce it — see 08-testing.md);
  • MUST have a typed output schema;
  • MAY be subject to policies (including hiding them from unauthorized consumers);
  • SHOULD return semantic, minimal data — never a dump of internal component state, never more than the consumer is authorized to see;
  • are executed on demand via invocation; they are never eagerly embedded in snapshots (see Snapshot below and decision D5 in 13-open-questions.md).

Actions

Change presentation state. Every action declares: canonical name, description, input schema, optional output schema, handler, effect category, idempotence, reversibility, policies, confirmation requirement, and optional preconditions. Full definition shape in 03-core-api.md.

Procedure references

A declaration that an existing domain procedure is relevant in the current view, optionally with UI-derived input bindings and extra frontend policy (typically confirmation UX). A procedure reference is not a second tool: its identity is the procedure's canonical identity, and execution flows through the normal oRPC client to the authoritative server procedure. Full model in 05-orpc-integration.md.

The identity ladder

agent-surface distinguishes five identity levels. Confusing them is the root cause of most agent-UI bugs, so the distinction is normative.

text
component type   devices.table            what kind of surface unit this is
instance         devices.table @ main     one mounted occurrence of that type
registration     reg_01H8...              one mount lifetime of that instance
capability       view:devices.table.selectRows   one operation of that type
entity           device_456               a domain object referenced in inputs/outputs
  • Component type — a stable, code-reviewed identifier for a kind of agent component (devices.table). Part of the public surface contract.
  • Instance ID — distinguishes simultaneous mounts of the same type (main, comparison, or an entity id for per-row components). Chosen by the application from data, never from DOM order or render index. Defaults to "default".
  • Registration ID — an opaque, runtime-generated identifier for one mount lifetime. It changes on every remount and is the precise handle for staleness detection. Never stable, never authored.
  • Capability ID — the canonical name of one operation: plane:componentType.capabilityName.
  • Entity ID — application-domain identifiers (device_456) that flow through inputs and outputs. The library treats them as opaque payload; authorizing them is the server's job.

Canonical ID grammar (Draft)

text
capability-id   = plane ":" component-type "." capability-name
plane           = "view" | "domain"
component-type  = segment *( "." segment )
segment         = lowercase-letter *( lowercase-letter / digit / "-" )
capability-name = lowercase-letter *( letter / digit )        ; camelCase, no dots
instance-id     = 1*( letter / digit / "-" / "_" )            ; app-chosen, data-derived
  • Parsing is unambiguous: the capability name is everything after the last dot; segments never contain uppercase, capability names never contain dots. Underscores are reserved (they are used by the wire-name encoding, 09-adapters.md).
  • For domain: IDs, the part after the prefix is the canonical oRPC procedure path (e.g. devices.disable) and is treated as opaque — agent-surface does not parse it further.
  • Maximum ID length: 128 characters. Registration MUST reject IDs violating the grammar (INVALID_DEFINITION, see 07-errors.md).
  • Identity MUST NOT be derived from visible text, screen position, CSS selectors, or DOM structure.

Examples:

text
view:devices.table.selectRows
view:devices.filters.setStatus
view:app.navigation.goTo
domain:devices.disable

No cross-plane duplication

domain:devices.disable and view:devices.disable MUST NOT both exist. A frontend capability may prepare, contextualize, or present a domain operation, never redefine it. The registry detects suspicious suffix collisions between registered view capabilities and known domain procedure paths and warns (configurable to error) — see 03-core-api.md and D4.

Effect taxonomy

ts
type AgentEffect =
  | "read"                 // observations only (implicit)
  | "local-state"          // view actions: mutate local/view state
  | "navigation"           // view actions: change route or top-level view
  | "server-query"         // procedure references only
  | "server-mutation"      // procedure references only
  | "external-side-effect" // procedure references only (email, webhook, ...)
  | "destructive";         // procedure references only (hard to undo)

Decision (Draft, stable intent): server-query, server-mutation, external-side-effect, and destructive are reserved for procedure references. View actions MUST declare local-state or navigation; registering a view action with a server effect fails with PLANE_VIOLATION. Rationale and alternatives in D-effects, 13-open-questions.md. A "destructive but local" case (e.g. clearing an unsaved draft) is modeled as local-state with reversible: false and confirmation: "required" — the orthogonal properties below carry that weight.

Navigation completion (D23, normative). A navigation action's success is defined by its handler settlement, never by its owner's unmount timing: the handler resolves when the host router accepts or commits the transition (synchronous routers resolve trivially), and a successful navigation that unmounts its own registering component still reports ok. Unregistration never overwrites an in-flight navigation result; it only aborts the cooperative signal. Full rules in 03 §concurrency.

Orthogonal properties and defaults

Every capability additionally declares:

ts
{
  idempotent: boolean;                          // same input twice ⇒ same end state, no extra effect
  reversible: boolean;                          // the user can trivially undo it in the UI
  confirmation: "never" | "optional" | "required";
  audit: "none" | "metadata" | "full";
}

Defaults by kind/effect (applied when the author omits the property):

Kind / effectidempotentreversibleconfirmationaudit
Observation (read)true (forced)"never""none"
Action local-statefalsetrue"never""metadata"
Action navigationfalsetrue"never""metadata"
Procedure server-querytrue"never""metadata"
Procedure server-mutationfalsefalse"optional""metadata"
Procedure external-side-effectfalsefalse"required""full"
Procedure destructivefalsefalse"required" (cannot be lowered below "optional")"full"

confirmation: "optional" means: not required by the capability itself, but a policy or the consumer's configuration MAY escalate it to required. The runtime — never the model — has final authority on whether an invocation proceeds.

Layer/target/effect metadata

Descriptors expose plane and effect; adapters SHOULD render them into human/model-readable prefixes so the model can distinguish, at a glance:

  • a query used for its own reasoningplane: "domain" or observation, effect read/server-query;
  • an action changing what the user seesplane: "view", effect local-state/navigation;
  • a mutation of authoritative application stateplane: "domain", effect server-mutation/destructive.

Availability, visibility, and the deny-by-default rule

A capability, at any instant, is in exactly one of three states for a given consumer:

StateIn snapshot?Invocable?Produced by
availableyesyesmounted + enabled + when() true + no policy objection
unavailable (visible-disabled)yes, with available: false and unavailableReasonno → CAPABILITY_NOT_AVAILABLEwhen() false, enabled: false, or a policy returning disable
hiddennono → CAPABILITY_NOT_FOUNDnot registered, unmounted, or a policy returning hide

Normative rule (D11/D12): authority hides, state discloses.

  • Lack of authorization (the consumer/user may never do this) → hidden. Existence of a capability is itself information; deny-by-default means not leaking it.
  • Invalid state (the user could do this, but not right now: no rows selected, drawer already open) → visible-disabled with a reason. This is deliberately disclosed because it lets the agent plan ("select rows first, then domain:devices.disable becomes available").

Built-in policies follow this rule; custom policies choose explicitly (hide vs disable decisions, 06-policies-and-security.md).

Mounted ≠ visible. Unmounting always removes capabilities, but some UI patterns keep components mounted while hidden (inactive tabs, off-screen virtualized content, Activity/keep-alive). Such components MUST gate themselves with enabled: false (or when) while not presented to the user. The React adapter documents the pattern (04-react-api.md).

Surface, snapshot, and version

  • The surface is the live set of registrations plus their availability, as filtered by policies for a given consumer.
  • A snapshot is an immutable, serializable capture of the surface at one instant: a catalog of components and capability descriptors (schemas, descriptions, availability, binding metadata) — never handlers, never eagerly-read state.
  • surfaceId is a random identifier minted per registry instance (per page load). surfaceVersion is a monotonically increasing integer (serialized as string) scoped to that surfaceId, incremented on every surface-affecting mutation. Together they order and scope snapshots; details and rationale in 03-core-api.md and D1.
  • Staleness: an invocation may carry the registrationId (and optionally surfaceVersion) it discovered. If the target registration has since been replaced, the invocation fails with STALE_CAPABILITY; if it is gone, with COMPONENT_UNMOUNTED. The registry never keeps executable references to unmounted components.

The lazy interaction model

To avoid shipping application state into the model's context, consumers follow four steps, each explicit and policy-checked:

  1. discover components — snapshot, catalog only;
  2. discover capabilities — descriptors with schemas and availability;
  3. invoke targeted observations — read exactly the state needed;
  4. execute actions / procedures — with staleness, policy, and confirmation enforcement.

Consumers

A consumer is the identified agent-side client of a surface: the embedded copilot, a WebMCP peer, a test harness. Consumers carry an id, a kind, and optional grants; snapshots and invocations are evaluated per consumer. An agent MUST NOT be able to invoke a capability that was not present in the surface granted to it — enforced because invocation re-runs the same policy pipeline that filtered the snapshot (06-policies-and-security.md).

Policies

Composable middleware attached at registry, component, or capability level, evaluated both at discovery (synchronously, to filter/disable) and at invocation (asynchronously, authoritatively for the client side). Discovery-time evaluation is an optimization and a UX aid; invocation-time evaluation is the client-side gate; the server remains the real gate for domain operations. Full model in 06-policies-and-security.md.

Confirmation

A confirmation dialog is a representation of policy, not the policy. The runtime returns a typed CONFIRMATION_REQUIRED outcome with a single-use, expiring confirmation record bound to a canonical digest of the exact request — surface, registration, capability, consumer, validated effective input, and effect — so what the user approves is what executes, bound values included (D21). The host renders UI; the user resolves; the agent (or the adapter on its behalf) retries with the confirmation evidence; for domain procedures the server MAY additionally require its own approval. Protocol in 06-policies-and-security.md.

Trust of registrants

Registrations are code running in the page, but not all page code is equally trusted (third-party widgets, embedded iframes with access to the app bundle, plugins). Each registration carries an origin label (default "first-party"); the registry can be configured with a registration guard that rejects or restricts registrations from other origins. This distinguishes first-party capabilities from capabilities registered by less-trusted code without pretending the browser gives real isolation — see the honest limits in 06-policies-and-security.md.

Glossary (quick reference)

TermMeaning
agent surfaceThe set of capabilities currently exposed to a consumer
agent componentA registration unit grouping capabilities under a component type + instance
capabilityObservation, action, or procedure reference
planedomain (backend-authoritative) or view (presentation)
descriptorThe serializable, agent-visible description of a component/capability
snapshotImmutable capture of the surface catalog for a consumer
registryThe core runtime holding registrations and dispatching invocations
consumerAn identified agent-side client of the surface
adapterBridge between the registry and a consumer/transport
policyMiddleware deciding visibility and invocability
bindingUI-derived values pre-filling (and usually locking) procedure inputs
confirmation evidenceSingle-use, expiring proof that the user approved one specific invocation
stalenessAn invocation referencing a registration that no longer exists or was replaced
entityA domain object id flowing through payloads; opaque to the library

Specification + v0.1 implementation, published on npm as 0.x. Nothing is Stable yet.