Your First Channel
OpenBot organizes conversation traffic under channels and threads. A channel is still the workspace boundary for specs, state, and tooling scopes; a thread is one concrete conversation inside that channel.
Channel-level conversations are disabled: every chat-capable flow uses thread scope only. For GET /api/events, POST /api/publish, and GET /api/state, you must supply both channelId and threadId together with agentId when you expect messages to route and persist correctly. Omitting either identifier is not supported for conversations.
For defining who answers inside that scope, see Your First Agent.
Prerequisites
Section titled “Prerequisites”- OpenBot installed and running (
npm i -g openbot, thenopenbot start). - Familiarity with the small HTTP surface in the upstream README API section.
1. How correlation IDs reach the server
Section titled “1. How correlation IDs reach the server”For GET /api/events, POST /api/publish, and GET /api/state, the OpenBot server resolves context from HTTP headers, then query string, then JSON body (where applicable). The canonical headers (reference implementation) are:
| Field | Header |
|---|---|
channelId | x-openbot-channel-id |
threadId | x-openbot-thread-id |
agentId | x-openbot-agent-id |
runId | x-openbot-run-id |
Equivalent keys channelId, threadId, agentId, and runId may appear as query parameters or, for publish/state bodies, alongside your event payload.
Conversation rule: pass non-empty channelId and threadId on every SSE subscription and publish/state call that participates in messaging. Whitespace-only ids should be rejected the same way as missing ids.
2. Pick ids before you integrate
Section titled “2. Pick ids before you integrate”Choose stable string identifiers for URLs and disk paths, for example channel engineering with thread ticket-4821, or channel workshop-alpha with thread lesson-01.
You do not need a separate RPC call before first use for many setups:
- When you publish with a
threadIdwhose folder does not exist yet, the storage layer cancreateThreadautomatically before appending history (storeEvent). - For channels where you want
SPEC.mdandstate.jsoncreated deliberately (recommended when humans collaborate inside OpenBot), use thecreate_channeltool fromstorage-toolson the built-insystemagent (definitions), or mirror those files under~/.openbot/channels/<channelId>/followingstorageService.createChannel.
3. Subscribe on the SSE stream
Section titled “3. Subscribe on the SSE stream”Always include both correlation headers (or query fields):
curl -N \ -H "x-openbot-channel-id: workshop-alpha" \ -H "x-openbot-thread-id: lesson-01" \ "http://localhost:4132/api/events"The subscriber fan-out key is channelId + threadId (concatenated internally as channelId:threadId) — there is no separate “whole channel” conversation stream anymore (getClientKey).
When channelId equals __global__, the stream can still emit coarse agent:run:* lifecycle notifications for dashboards (logic). That path is distinct from user-facing threads.
4. Publish into that bucket
Section titled “4. Publish into that bucket”Use the same channelId and threadId on publish as on SSE:
curl -X POST "http://localhost:4132/api/publish" \ -H "content-type: application/json" \ -H "x-openbot-channel-id: workshop-alpha" \ -H "x-openbot-thread-id: lesson-01" \ -H "x-openbot-agent-id: study-buddy" \ -d '{ "type": "user:input", "data": { "content": "Give me a short recap on threading vs channels." } }'The dispatcher accepts user:input as well as agent:invoke (routing notes); README examples typically show user:input.
5. Where this lands on disk
Section titled “5. Where this lands on disk”Under your configured base directory (default ~/.openbot):
~/.openbot/channels/<channelId>/SPEC.md # when created explicitly via storage tooling~/.openbot/channels/<channelId>/state.json
~/.openbot/channels/<channelId>/threads/<threadId>/state.json~/.openbot/channels/<channelId>/threads/<threadId>/events.jsonl # conversation historyConversation transcripts live only under threads/<threadId>/. Channel roots hold workspace metadata (SPEC.md, state.json), not channel-wide chat logs.
DEFAULT_CHANNELS_DIR resolves to channels; getConversationDir resolves channel vs thread paths.
- Clients: Prefer OpenBot.one for channel/thread/agent UX instead of curl (OpenBot.one client); the CLI hints at this URL when you run
openbot start. - Agents: Custom ids (
study-buddy, etc.) must align betweenAGENT.mdfolders and theagentIdyou pass through HTTP headers/query/body. - Plugins: Emit
agent:output/client:ui:widgetwith the samethreadIdyou used on ingress whenever you rely on thread-scoped correlation (PluginContext,agentOutput).
Next steps
Section titled “Next steps”- OpenBot.one client — hosted UI wired to your local runtime.
- Your First Agent — install plugins/instructions for a dedicated responder inside your scope.
- Architecture — event bus overview tying SSE fan-out to the harness.