Skip to content

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.

  • OpenBot installed and running (npm i -g openbot, then openbot start).
  • Familiarity with the small HTTP surface in the upstream README API section.

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:

FieldHeader
channelIdx-openbot-channel-id
threadIdx-openbot-thread-id
agentIdx-openbot-agent-id
runIdx-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.

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 threadId whose folder does not exist yet, the storage layer can createThread automatically before appending history (storeEvent).
  • For channels where you want SPEC.md and state.json created deliberately (recommended when humans collaborate inside OpenBot), use the create_channel tool from storage-tools on the built-in system agent (definitions), or mirror those files under ~/.openbot/channels/<channelId>/ following storageService.createChannel.

Always include both correlation headers (or query fields):

Terminal window
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.

Use the same channelId and threadId on publish as on SSE:

Terminal window
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.

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 history

Conversation 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 between AGENT.md folders and the agentId you pass through HTTP headers/query/body.
  • Plugins: Emit agent:output / client:ui:widget with the same threadId you used on ingress whenever you rely on thread-scoped correlation (PluginContext, agentOutput).