Skip to content

React API ​

@agent-surface/react connects compiled component contracts to mounted React state. Its public registration API is contract-first: static semantics come from the compiler-authorized contract; the hook accepts only live runtime bindings.

Provider ​

Create one registry for the application and provide it above every registering component:

tsx
import authority from "virtual:agent-surface-contract";
import { createAgentSurfaceRegistry } from "@agent-surface/core";
import { AgentSurfaceProvider } from "@agent-surface/react";

const registry = createAgentSurfaceRegistry({ authority });

root.render(
  <AgentSurfaceProvider registry={registry}>
    <App />
  </AgentSurfaceProvider>,
);
ts
interface AgentSurfaceProviderProps {
  registry: AgentSurfaceRegistry;
  children: ReactNode;
}

function useAgentSurface(): AgentSurfaceRegistry;

useAgentSurface() throws when no provider is present.

Component binding ​

tsx
const handle = useAgentComponent(devicesTableContract, {
  instanceId: props.instance,
  observations: {
    readState: { read: () => ({ rows, selectedIds }) },
  },
  actions: {
    selectRows: {
      when: () => rows.length > 0,
      execute: ({ ids }) => setSelectedIds(ids),
    },
  },
});

The compiled contract owns:

  • component and capability identity;
  • descriptions and schemas;
  • effects, reversibility, confirmation, tags, and policy attachments.

The runtime binding owns:

  • handlers and current state;
  • instanceId;
  • enabled and per-capability when gates;
  • preconditions and contextual metadata.

The return value reports the current registration:

ts
interface AgentComponentHandle {
  registrationId: string | undefined;
  status: "active" | "rejected" | "unregistered" | "pending";
}

Lifecycle ​

  • registration happens in a React effect;
  • cleanup unregisters the complete component surface;
  • Strict Mode effect replay produces no leaked registration;
  • changing type or instanceId creates a new registration;
  • structural contract semantics stay fixed for one registration;
  • changes to enabled or when are pushed to the registry after render;
  • unmount aborts active non-navigation invocations and rejects stale references.

Handler freshness ​

Handlers are read through an internal latest-value reference at invocation time. They see current props and state without a dependency array or useCallback. Updating a handler closure does not change capability identity or surfaceVersion.

Structural changes are different: identity, schemas, descriptions, effects, and policy shape cannot change under an existing registrationId. If a live binding changes structure, the hook reports the authoring error and registers a new immutable descriptor.

Visibility ​

Use enabled: false when a component remains mounted but is not presented, such as an inactive tab or keep-alive route. Use per-capability when for state-specific availability. React pushes gate changes to the registry after each commit so adapters can refresh the surface.

Authority decisions and UI state remain distinct: policies may hide a capability; enabled and when expose it as unavailable with a reason.

Anti-patterns ​

  • Do not derive contract identity, descriptions, schemas, effects, or policy attachments from props or state.
  • Do not use a view: action to duplicate a backend procedure; bind a compiled procedure contract instead.
  • Do not keep an off-screen component enabled only because React keeps it mounted.
  • Do not capture discovery results as execution authority; invocation re-checks live state and policy.
  • Do not construct a registry inside component render. Create one at the application composition root.

Domain procedure binding ​

Use @agent-surface/orpc/react to attach UI context to a compiled procedure contract:

tsx
import { useAgentProcedure } from "@agent-surface/orpc/react";

useAgentProcedure(devicesDisableContract, bridge.refs.devices.disable, {
  bind: () => ({ deviceIds: selectedIds }),
  when: () => selectedIds.length > 0,
});

The contract provides the domain identity and governance metadata. The bridge reference provides authoritative execution. The runtime config provides live bindings and availability. See oRPC integration.

Confirmation UI ​

ts
const pending = usePendingConfirmations();

usePendingConfirmations() subscribes to registry confirmation events and returns the pending records the host must render. Resolve a record through registry.confirmations.approve() or .deny(); do not construct confirmation evidence in component state.

Compiler-authorized frontend capabilities for agents. Source on GitHub.