Extend · Implementation

Build an extension

Choose a current contract, run a small Go room agent, and define its authority, lifetime, and failure behavior.

Choose an extension contract based on what triggers the work, what it returns, and which component owns its lifetime. A client event consumer, an embedded room agent, a requested task, and a native media adapter have different responsibilities.

Choose the smallest contract

Trigger and result Use today Who wires it
A UI view needs selected facts SDK subscription Client application
A committed channel event should produce another fact Embedded Go RoomAgent Trusted RMC host
A client requests an objective with progress and a terminal result DelegationAdapter Trusted RMC host
A named operation should return a direct result ToolHandler Trusted RMC host
A provider needs join, lifecycle, or voice control integration MediaAdapter or VoiceAdapter Host and native client integration
An external service needs a narrow request/response integration Purpose-built HTTP adapter or authorized API client Integration owner

The integration catalog links existing implementations. There is no general remote TypeScript processor host or dynamic adapter installation endpoint.

Run an embedded extension end to end

This example consumes a committed chat message and publishes a derived measurement to another channel. It counts whitespace-separated words; it does not invoke a model or perform an external side effect.

From the repository root, with its Go toolchain available:

Terminal
go run ./docs/examples/room-agent

It creates temporary SQLite storage, registers the agent, provisions two channels, authenticates a scoped client, publishes one message, and checks the derived event. The program closes its subscriptions/runtime and removes the temporary database on exit. It does not use provider credentials, load a local environment file, or open a listening port.

A customer publishes text.message.committed to chat. MessageStatsAgent has grants to read chat and publish measurements. It validates the text, counts whitespace-separated words, and returns demo.message.measured under its agent identity, referencing the original event and preserving visibility.
The host wires the agent once. Clients observe its derived output through ordinary authorized pub/sub.Open SVG ↗Excalidraw source ↓

After validating the input and encoding payload, the handler returns this derived event:

Go
return []rmc.EventInput{{
    ChannelID: "measurements", Type: "demo.message.measured",
    CausationID: request.Event.ID,
    Visibility: request.Event.Visibility,
    Payload: payload,
}}, nil

This is an excerpt from Handle. The complete file includes the descriptor, scoped grants, cancellation check, input validation, and word-count calculation.

Download the agent and host/bootstrap program. Keep both files together to run the example from the repository. The host demonstrates direct registration through registry.RegisterRoomAgent(...); only bootstrap code uses privileged Runtime and Store operations.

Make inputs and outputs explicit

The descriptor subscribes to text.message.committed on chat. The agent has permission to read that channel and publish to measurements; it does not receive wildcard grants. RMC filters its triggering event, snapshot, and bounded history before invoking the handler.

The handler validates the input it needs and emits demo.message.measured. Its payload references the source event, and it preserves the input visibility. RMC assigns the agent actor and validates the returned publication. The original message remains immutable.

The host restricts the input to the client and agent participant IDs. The example checks that the output retains that visibility. Other participants still need both channel authority and visibility to read a result.

For your extension, document the accepted input schema, output schema, and compatibility policy. Derived output can reveal information about the input, so review its recipients as well as its payload. Before forwarding data to another service, check the application’s data-handling policy as well as the participant’s room grants.

Decide what happens on failure

Respect context cancellation. Keep work bounded. A handler error is recorded as an agent failure; the embedded worker is not a durable job queue that retries the input until an external effect succeeds.

At worker startup, the current room-agent implementation begins from the room's current position. It does not restore a persisted per-agent processed cursor and replay all missed triggers after a restart. If that recovery is part of your product contract, use an application-managed consumer with persisted progress or design a small purpose-built worker boundary. Do not assume the embedded example supplies it.

Returned event publications can have stable runtime-assigned retry identity. That does not make an external write inside Handle atomic with the resulting event. Keep external idempotency and restart behavior with the integration that performs the effect.

The behavior is defined in agent_runtime.go and tested in agent_runtime_test.go.

Treat in-process extensions as trusted code

Grants restrict the room data and publications passed through RMC. They do not sandbox Go code from the host's filesystem, network, or process credentials. Registering an untrusted package in the server gives it in-process execution.

Keep secrets in trusted deployment configuration and pass only the data needed for the integration. An external HTTP integration can provide a clearer process boundary, but it still needs explicit authentication, timeouts, data handling, and cancellation behavior. The current reasoner adapter is one narrow example.

Package the extension for another team

Publish a short entry with its owner, version, tested RMC revision, primary contract, input/output schemas, grants, external dependencies, and failure behavior. Include a runnable example and a small set of tests for the behavior the next team relies on.

Use the catalog-entry template. A host team can review the package, pin its revision, and register it in code today. The marketplace discussion explains what would need to exist before discovery became self-service installation.

Search the documentation

Type to search all guides.

Diagram

100%Open original ↗