Local Chatto runbook
Synced from
channels/docs/runbooks/chatto-local.md. The repository is the source of truth.
Use this runbook to verify one Chatto mention through the body-free wake,
channel_read, threaded channel_respond, and notification dismissal flow.
Compatibility boundary
Section titled “Compatibility boundary”The adapter vendors the schema from
chattocorp/chatto@ee0425759941501e5f123f9dbeb7b5ecdcc5699e.
That revision implements realtime protocol v2 and the resumable
notifications_replace projection. These features are newer than Chatto
v0.4.16, so that tagged release is not compatible with this adapter. Test with
a server built from the pinned revision or a later release that retains the
same protocol and ConnectRPC schema. The source pin and regeneration details are
also recorded in
extensions/vendor/chatto/SCHEMA.md.
As of 2026-07-25, automated adapter tests pass. A live smoke test has now run: realtime protocol v2 was negotiated with capability chatto.realtime.projection.v1, and a mention-to-reply round trip completed against a live server. The 0.4 release line is a separate branch without the protocol v2 commit, so no published 0.4.x release can serve this adapter; only a build from main works.
Prerequisites and permissions
Section titled “Prerequisites and permissions”You need:
- a compatible self-hosted Chatto server reachable from the workstation;
- two identities: the bot and a human tester;
- a bearer token for the bot;
- a dedicated room containing both identities; and
- Node.js, npm, and Pi installed locally.
The bot must be able to read the room timeline and its own notifications, post top-level and thread messages, and dismiss its own notifications. Restrict the bot to the dedicated room when the deployment’s membership and token controls allow it.
Configure and start
Section titled “Configure and start”From the repository root, validate the checkout:
npm installnpm run checkEnter the token without placing it in shell history, then start only Chatto:
export CHATTO_BASE_URL="https://chatto.example.com"read -rsp "Chatto bot token: " CHATTO_TOKEN; echoexport CHATTO_TOKENexport CHATTO_ROOM_IDS="room-id"export OUTFITTER_CHANNELS="chatto"pi -e ./extensions/index.tsCHATTO_BASE_URL must be the HTTP(S) server origin. Omit CHATTO_ROOM_IDS only
when every room visible to the bot is intentionally in scope for intake and
replies. Omission disables top-level publication. channel_publish always
requires an explicit target entry in CHATTO_ROOM_IDS. A partial configuration
is detected at startup and does not prevent other selected channels from
starting.
Verify top-level publication
Section titled “Verify top-level publication”Call channel_publish with channel: "chatto", the allowlisted room ID as
target, a stable operation_id, and the message body as content.
Confirm that Chatto creates a top-level room message. A retry with the same
operation ID and content must return the first message ID without a second post.
A retry with changed target or content must fail.
If the tool reports an ambiguous provider result, do not retry it. Stop Pi so the operator command has exclusive access to the task-plane store. Inspect Chatto for the intended message. If it exists, record its provider message ID:
export CHANNELS_TASK_STORE_PATH="$HOME/.local/share/outfitter/channels/task-plane"outfitter-channel-reconcile chatto OPERATION_ID delivered PROVIDER_MESSAGE_IDIf the message does not exist and the provider result is confirmed absent, mark the operation retryable:
outfitter-channel-reconcile chatto OPERATION_ID retryableRestart Pi after reconciliation. Never use retryable while the provider
result remains unknown because a retry can create a duplicate message.
Verify the round trip
Section titled “Verify the round trip”- Wait for the resident session to start without a Chatto authentication or protocol error.
- As the human tester, post one top-level message that mentions the bot.
- Confirm the wake contains a
chatto:v1:...locator but no message text, room name, sender name, or other user-controlled content. - Call
channel_readwith that locator. Confirm it returns the exact target, no more than ten messages, andhandled: false. - Call
channel_respondonce. Confirm Chatto creates exactly one reply in a new thread rooted at the mention and reportsreplied: true, handled: true. - Call
channel_readagain. Confirm the dismissed notification maps tohandled: true. - Repeat with a mention inside an existing thread. Confirm the reply stays in that thread.
- Stop Pi and confirm the realtime connection closes. Restart and confirm a new mention still wakes the session.
For coexistence, repeat with OUTFITTER_CHANNELS=chatto,slack. Deliberately make
the Chatto token invalid and confirm Slack still starts, then restore the token
and restart.
If the reply succeeds but dismissal is denied, the tool must report
replied: true, handled: false with a warning. Record the warning; do not retry
the reply automatically.
Troubleshooting and cleanup
Section titled “Troubleshooting and cleanup”| Symptom | Check |
|---|---|
| Protocol negotiation fails | Confirm the server is built from the pinned revision or a later protocol-v2-compatible release, not v0.4.16. |
| Authentication fails | Confirm the token belongs to the bot identity and is valid for this server origin. |
| No mention wake arrives | Confirm both identities are room members, the event creates a mention notification, and the room ID is allowlisted. |
| Read fails after a wake | Confirm the notification and message still exist and the bot retains room/timeline access. |
| Reply succeeds but handled is false | Grant notification-dismiss permission or fix the server error; do not resend the reply. |
| Publication is ambiguous | Stop Pi, inspect Chatto, reconcile the operation as delivered or confirmed retryable, then restart Pi. |
When finished, stop Pi and clear the secret:
unset CHATTO_TOKEN CHATTO_BASE_URL CHATTO_ROOM_IDS OUTFITTER_CHANNELSRotate the token immediately if it was exposed in source, logs, shell history, or chat.