Skip to main content
MCP Channels is the ChatCLI push notification system. MCP servers send proactive messages (alerts, CI events, deploys, external webhooks) and ChatCLI stores them in a durable ring, injects them into the next chat / agent / coder turns, and โ€” optionally โ€” triggers the agent automatically via a rules engine with three operating modes.
Channels work across all three MCP transports supported by ChatCLI:
  • sse (classic HTTP+SSE) โ€” persistent listener on the GET /sse stream.
  • http (Streamable HTTP, MCP spec 2025-03-26) โ€” opt-in listener via GET on the configured endpoint.
  • stdio โ€” any JSON-RPC message without id emitted by the child process is treated as a notification.
Delivery is passive by default (a discreet banner on the next prompt) and reactive by explicit opt-in (confirm or auto rules in ~/.chatcli/mcp/triggers.json).

Architecture at a glance

MCP Server                    ChatCLI
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€                    โ”€โ”€โ”€โ”€โ”€โ”€โ”€

  notification โ”€โ”€โ”€โ”€โ”€โ–บ  โ”Œโ”€โ”€โ”€โ”€ Transport listener (sse / http / stdio) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                       โ”‚                                                    โ”‚
                       โ”‚  routes by JSON-RPC method:                        โ”‚
                       โ”‚    notifications/<channel>  โ†’  channel = <channel> โ”‚
                       โ”‚    channel/message          โ†’  channel = params... โ”‚
                       โ”‚    notifications/initialized โ†’ swallowed (control) โ”‚
                       โ”‚    any other method         โ†’  channel = method    โ”‚
                       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                             โ–ผ
                       โ”Œโ”€โ”€โ”€ ChannelManager โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                       โ”‚                                                    โ”‚
                       โ”‚  1. Subscription filter (ServerConfig.channels)    โ”‚
                       โ”‚  2. Sequence stamp (monotonic, per-instance)       โ”‚
                       โ”‚  3. In-memory ring buffer (default 200 msgs)       โ”‚
                       โ”‚  4. Append-only JSONL persistence (~/.chatcli/mcp) โ”‚
                       โ”‚  5. Fan-out to OnMessage subscribers (engine, UI)  โ”‚
                       โ”‚  6. Unread counter + LastViewedSeq                 โ”‚
                       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                             โ–ผ
       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
       โ–ผ                                                                     โ–ผ
โ”Œโ”€ Trigger engine (rules) โ”€โ”                          โ”Œโ”€ System-prompt injector โ”€โ”
โ”‚  match server/channel    โ”‚                          โ”‚  chat:   mcpChannelPart   โ”‚
โ”‚  /content + rate-limit / โ”‚                          โ”‚  agent:  buildAgentSys-   โ”‚
โ”‚  dedup window            โ”‚                          โ”‚  coder:  Message channels โ”‚
โ”‚  โ†’ Action (notify /      โ”‚                          โ”‚  block (uncached so it    โ”‚
โ”‚     confirm / auto)      โ”‚                          โ”‚  never thrashes cache)    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
             โ–ผ
โ”Œโ”€ CLI pending queues โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  notify  โ†’ inbox banner above next prompt            โ”‚
โ”‚  confirm โ†’ toast + /channel confirm <id> hint        โ”‚
โ”‚  auto    โ†’ drained at next prompt tick, runs agent   โ”‚
โ”‚            inside AUTO-AGENT envelope                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Each box is a real source file (cli/mcp/channels.go, cli/mcp/triggers/triggers.go, cli/channel_triggers.go, cli/agent_system_prompt.go). The split is deliberate: ChannelManager is process-local and independent of the CLI; the trigger engine is a separate package without a reverse dependency; the CLI plugs in via OnMessage.

How a message reaches the agent

1

Server emits notification

MCP server sends a JSON-RPC message without id (a notification in the spec). Can be over the SSE stream, the GET of Streamable HTTP, or a stdout line in stdio.
2

Transport extracts and routes

The matching transport parses, identifies it as a notification (id absent) and calls ChannelManager.ProcessSSENotification. The method becomes the channel: notifications/ci-pipeline โ†’ channel ci-pipeline; channel/message with params.channel โ†’ channel params.channel.
3

Subscription filter

If the server config has channels: [...] and the channel is not listed, the message is dropped (debug log). Empty list means โ€œaccept everythingโ€.
4

Persist + ring + unread

Message gets a monotonic seq, lands in the in-memory ring (FIFO, default 200), is append-only written to ~/.chatcli/mcp/channels.jsonl, and the unread counter increments.
5

Fan-out to subscribers

Each registered OnMessage callback receives a copy. The CLI registers two: the trigger engine (evaluates rules) and the banner renderer (prepares the UI).
6

System prompt โ€” next turn

On the next chat/agent/coder turn, the 5 most recent messages from the ring are injected as a block into the system prompt (uncached, so the cached prefix is not invalidated).
7

Reactive trigger (optional)

If any rule matched, the engine emits an Action that lands in one of three queues: notify (discreet banner), confirm (yes/no prompt), auto (runs the agent at the next prompt tick).

Persistence

~/.chatcli/mcp/
โ”œโ”€โ”€ channels.jsonl       โ† active file, append-only
โ””โ”€โ”€ channels.jsonl.1     โ† rotated (single backup)

Guarantees

  • Durable across sessions: messages received while ChatCLI was closed are visible on the next boot (up to the load limit).
  • Append-only: we never rewrite the file. A process crash mid-write leaves at worst one truncated line โ€” the loader skips line-by-line, so a single corrupt line never poisons the rest.
  • Automatic rotation: when the file reaches 10 MiB, it is renamed to .1 and a fresh file is opened. We keep one historical file โ€” channels are telemetry, not forensic audit log.
  • Best-effort writes: if the write fails (disk full, permissions), ChatCLI logs a warning once, marks persistence as disabled only for that session, and keeps serving the in-memory ring.

Boot โ€” replay

On startup, ChatCLI reads the last 200 lines combined from channels.jsonl.1 + channels.jsonl in chronological order. Replayed messages:
  • enter the ring;
  • do not count as unread (they were already seen);
  • preserve their original seq (the internal counter is advanced past the highest observed).

How to configure

Persistence is on by default when HOME is resolvable. In containerized environments without HOME (rare), ChannelManager falls back to in-memory-only and logs an info on startup. There is no flag to โ€œdisable persistenceโ€ โ€” to wipe the state, delete the file manually (rm ~/.chatcli/mcp/channels.jsonl*).

Per-server filter โ€” channels

The optional channels field on the MCP server config is an allow-list that decides which channels from that server are accepted into the ring.
{
  "name": "prom-alerts",
  "transport": "sse",
  "url": "https://prom-alerts.internal/sse",
  "enabled": true,
  "channels": ["alerts/critical", "alerts/error"]
}
Rules:
  • Empty/omitted โ†’ accepts every channel the server emits.
  • Explicit "*" โ†’ equivalent to empty.
  • List โ†’ only literally listed channels pass; others are dropped (debug log).
  • Whitespace in entries is trimmed, so " alerts " matches "alerts".
Filtering happens at receive time (before the ring and persistence), so a noisy server emitting 20 channels when you only care about 2 doesnโ€™t pollute anything.
Matching is literal (no glob support here). "channels": ["alerts"] does not match "alerts/critical". Use the exact channel name the server emits. Globs (alerts/*) only exist in trigger rules, which run after this filter.

Auto-injection into the system prompt

On every turn (chat, agent, coder), the 5 most recent ring messages are added to the system prompt as an extra block:
## MCP Channel Messages (Recent)

[prom-alerts/alerts/critical 14:32:45] api-prod-3 memory > 90%
[ci-monitor/ci-pipeline 14:33:10] PR #234 build failed: lint errors
[prom-alerts/alerts/critical 14:33:30] payment-svc p99 > 500ms
[ci-monitor/ci-pipeline 14:35:00] PR #234 retried by author
[ci-monitor/deploys 14:35:42] canary-3 rollout started

Important guarantees

  • No cache hint: this block is volatile and intentionally lives outside the Anthropic cached prefix. Putting something that changes every turn inside the cache would trash the entire KV cache โ€” worse than not caching at all.
  • Same content in chat / agent / coder: the system-prompt builders for all three modes were unified to include the block.
  • Empty ring โ†’ block omitted: zero overhead when you have never received anything.

Why only 5?

Calibrated for balance: enough to give recent context (CI running + hottest alert) without using too many tokens when the user has multiple chatty servers. If you need more history for a specific turn, use /channel inject (which injects the most recent 10).

Reactive Triggers (rules engine)

This is the opt-in part. When you want ChatCLI to react to events (e.g., โ€œif CI failed, ask the agent to investigateโ€; โ€œif thereโ€™s a critical alert, run the agentโ€), define rules in ~/.chatcli/mcp/triggers.json.

Rule schema

{
  "rules": [
    {
      "name": "ci-investigator",
      "server": "ci-monitor",
      "channel": "ci-pipeline",
      "contentRegex": "(?i)failed|broken|error",
      "mode": "notify",
      "prompt": "Investigate this CI failure: {{content}}",
      "tools": ["gh_pr_diff", "gh_pr_checks"],
      "rateLimit": "5m",
      "dedupWindow": "1m"
    }
  ]
}
Field reference:
FieldTypeRequiredDescription
namestringโœ…Unique rule identifier. Appears in logs and in /channel rules
serverstringโŒExact match against the MCP server name. Empty = any server. "*" = any (explicit)
channelstringโŒMatch. Supports literal ("alerts"), wildcard ("*") and prefix-glob ("alerts/*")
contentRegexstringโŒGo regexp against the message Content. Empty = any content
modestringโŒ"notify" (default), "confirm", "auto"
promptstringrequired for confirm/autoTemplate sent to the agent when the rule fires. Variables: {{content}}, {{channel}}, {{server}}, {{seq}}, {{timestamp}}
toolsstring[]required for autoWhitelist of tools the agent can invoke. In auto mode, validation rejects rules without tools to prevent unfettered autonomous access
rateLimitstring (Go duration)โŒPer-rule cap: after a fire, ignore matches within this window. Examples: "5m", "30s", "1h"
dedupWindowstring (Go duration)โŒPer (rule, content prefix) dedup: same rule + same prefix within the window is a no-op

Matching โ€” practical examples

{
  "name": "any-critical",
  "channel": "alerts/critical",
  "mode": "notify"
}
Matches exactly the alerts/critical channel on any server.
{
  "name": "all-alerts",
  "channel": "alerts/*",
  "mode": "notify"
}
Matches alerts/critical, alerts/warning, alerts/info โ€” any sub-channel under alerts/. Does not match errors/critical (different prefix).
{
  "name": "prom-only-paged",
  "server": "prom-alerts",
  "channel": "alerts/*",
  "contentRegex": "(?i)\\b(pager|p1|sev1)\\b",
  "mode": "confirm",
  "prompt": "We got paged: {{content}}. Investigate now?"
}
Triple condition: server prom-alerts, channel alerts/*, and content matching the regex. All must be satisfied (AND).

Modes

mode: notify (default โ€” zero surprise)

When the rule matches:
  1. Immediately: a toast prints on stderr with the rule name and a content preview.
  2. The next time you type (next prompt tick), a banner shows above the prompt with the item in the inbox.
  3. Nothing runs. You decide whether to act.
> _

โ•ญโ”€โ”€ ๐Ÿ“ก MCP CHANNEL INBOX โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚  3 new channel message(s) since last ack                         โ”‚
โ”‚  [prom-alerts/alerts/critical] any-critical  api-prod-3 mem >90% โ”‚
โ”‚  [prom-alerts/alerts/critical] any-critical  payment p99 > 500ms โ”‚
โ”‚  [ci-monitor/ci-pipeline]      ci-watcher    PR #234 lint failed โ”‚
โ”‚                                                                  โ”‚
โ”‚  Hint: /channel ack to clear, /channel list for full inbox       โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

>

mode: confirm (user-in-the-loop)

When the rule matches:
  1. Stderr toast + prompt in the banner: โ€œrun /channel confirm <id> yes or /channel confirm <id> noโ€.
  2. Nothing runs until the user answers.
  3. /channel confirm <id> yes (or just /channel confirm <id>, defaulting to yes) fires the agent with the rule template.
  4. /channel confirm <id> no discards the action.
Confirm actions expire after 30 min โ€” if you donโ€™t reply, they leave the queue so state doesnโ€™t grow indefinitely.
> /channel confirm 7 yes

โ•ญโ”€โ”€ ๐Ÿค– AUTO-AGENT โ–ถ ci-watcher   (ci-monitor/ci-pipeline) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚  PR #234 lint failed (3 errors in src/auth/middleware.go)         โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

โ–ธ thinking...
โ–ธ tool: gh_pr_diff 234
...

mode: auto (autonomous โ€” strong opt-in)

Requires tools whitelist (validation rejects auto rules without tools). When the rule matches:
  1. Stderr toast informing the trigger is queued.
  2. At the next prompt tick (after draining park resumes and before processing user input), the agent runs automatically with the rule template.
  3. Execution is rendered inside an AUTO-AGENT envelope box โ€” visually distinct from a normal turn response.
  4. Esc aborts as in any agent execution.
โ•ญโ”€โ”€ ๐Ÿค– AUTO-AGENT โ–ถ canary-canary-alert   (prom-alerts/alerts/critical) โ”€โ”€โ•ฎ
โ”‚  canary-3 p99 latency exceeded SLO budget for 5 minutes                 โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

โ–ธ thinking โ€” investigating SLO breach...
โ–ธ tool: kubectl_get pod -n canary -l rollout=v3
โ–ธ tool: kubectl_logs canary-3-7d8c... --tail=100
...

Guard-rails for auto

  • tools whitelist required โ€” startup validation rejects an auto rule without tools.
  • rateLimit and dedupWindow recommended โ€” without them, a noisy server can spawn dozens of turns per minute.
  • Esc always aborts, regardless of whether the agent was triggered by user or rule.
  • /channel pause shuts everything off globally โ€” use before a sensitive operation to avoid interference.

Rules configuration

~/.chatcli/mcp/triggers.json
Root schema:
{
  "rules": [ /* array of rules as described above */ ]
}
Ready-to-use examples:

CI watcher (notify only โ€” safe default)

{
  "rules": [
    {
      "name": "ci-failures",
      "server": "ci-monitor",
      "channel": "ci-pipeline",
      "contentRegex": "(?i)fail|broken|red",
      "mode": "notify",
      "rateLimit": "30s",
      "dedupWindow": "1m"
    }
  ]
}

Prod alerts โ†’ confirm

{
  "rules": [
    {
      "name": "prod-pages",
      "server": "prom-alerts",
      "channel": "alerts/critical",
      "mode": "confirm",
      "prompt": "We got a critical alert on prod:\n\n{{content}}\n\nInvestigate now?",
      "rateLimit": "5m",
      "dedupWindow": "2m"
    }
  ]
}

Canary deploys โ†’ auto (with tight whitelist)

{
  "rules": [
    {
      "name": "canary-sanity-check",
      "server": "deploy-tracker",
      "channel": "deploys/canary/*",
      "mode": "auto",
      "prompt": "A new canary deploy just started: {{content}}.\nRun the canary smoke checks and report.",
      "tools": ["kubectl_get", "kubectl_logs", "http_request"],
      "rateLimit": "1m",
      "dedupWindow": "30s"
    }
  ]
}

Validation

Schema failures reject the entire file (atomic apply โ€” either every rule lands, or nothing changes). Common errors:
SymptomCauseFix
mode "auto" requires a non-empty tools whitelistmode: "auto" without tools fieldAdd "tools": [...] or switch to mode: "confirm"
invalid contentRegex: error parsing regexpInvalid regexTest in regex101.com with โ€œGolangโ€ flavor; remember metachars need JSON double-escape (\\b, \\d)
invalid mode "Notify"Case-sensitiveUse lowercase: notify, confirm, auto
duplicate rule name "x"Two rules with the same nameNames must be unique within the file
invalid rateLimit "5min"Go time.Duration formatUse 5m, 30s, 1h (not 5min, 30sec)
Runtime reload: /channel rules reload.

/channel commands

All subcommands support autocomplete (Tab after /channel ).
SubcommandArgumentDescription
/channel or /channel listโ€”Lists up to 20 most recent ring messages with seq, timestamp, server, channel and content preview
/channel <name>channel nameFilters the listing by the given channel
/channel ackโ€”Marks all messages as read and clears the pending notify banner
/channel injectโ€”Splices the last 10 messages into the history as a system message for the next turn โ€” useful when you want to provide explicit context to the LLM without waiting for auto-injection of the 5 most recent
/channel pauseโ€”Pauses the trigger engine. Messages keep entering the ring/persistence, but no actions are emitted (no banner, no auto, no confirm)
/channel resumeโ€”Reactivates the trigger engine
/channel rulesโ€”Lists active rules with their modes, filters and prompts
/channel rules reloadโ€”Re-reads ~/.chatcli/mcp/triggers.json without restarting ChatCLI. On validation error, keeps the previous rules active
/channel confirm <id>id requiredAccepts a pending confirm action; defaults to yes
/channel confirm <id> noid requiredRefuses a pending confirm action without running anything
/channel run <seq>seq requiredManually triggers the agent on a specific ring message (use the seq shown by /channel list) โ€” useful to investigate something that came in as notify

Examples

Listing

> /channel list

โ•ญโ”€โ”€ ๐Ÿ“ก MCP CHANNELS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚   #15 14:32:45  prom-alerts/alerts/critical   api-prod-3 mem > 90%    โ”‚
โ”‚   #16 14:33:00  ci-monitor/ci-pipeline        PR #234 lint failed (3) โ”‚
โ”‚   #17 14:33:10  ci-monitor/deploys/canary     canary-3 rollout starts โ”‚
โ”‚                                                                       โ”‚
โ”‚   Total: 17 messages                                                  โ”‚
โ”‚   Unread: 3                                                           โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Filtering

> /channel ci-pipeline

โ•ญโ”€โ”€ ๐Ÿ“ก CHANNEL: ci-pipeline โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚   #16 14:33:00  ci-monitor/ci-pipeline   PR #234 lint failed (3)      โ”‚
โ”‚   #14 14:30:01  ci-monitor/ci-pipeline   PR #233 merged              โ”‚
โ”‚                                                                       โ”‚
โ”‚   Total: 17 messages                                                  โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Manually running a message

> /channel run 16

โ•ญโ”€โ”€ ๐Ÿค– AUTO-AGENT โ–ถ manual   (ci-monitor/ci-pipeline) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚  PR #234 lint failed (3 errors in src/auth/middleware.go)             โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

โ–ธ thinking โ€” investigating lint failures on PR #234...

Inspecting rules

> /channel rules

โ•ญโ”€โ”€ โš™ TRIGGER RULES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚  ci-failures   [notify]  server=ci-monitor channel=ci-pipeline        โ”‚
โ”‚    contentRegex: (?i)fail|broken|red                                  โ”‚
โ”‚    rate: 30s   dedup: 1m                                              โ”‚
โ”‚                                                                       โ”‚
โ”‚  prod-pages    [confirm] server=prom-alerts channel=alerts/critical   โ”‚
โ”‚    rate: 5m    dedup: 2m                                              โ”‚
โ”‚                                                                       โ”‚
โ”‚  canary-sanity [auto]    server=deploy-tracker channel=deploys/canary/* โ”‚
โ”‚    rate: 1m    dedup: 30s                                             โ”‚
โ”‚    tools: kubectl_get, kubectl_logs, http_request                     โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Auto-injection vs /channel inject โ€” when to use which

You wantCommandToken cost
The agent to always know about the last 5 messagesnothing โ€” automatic every turn~5 lines in the system prompt
Force explicit (and deeper) context for the next turn/channel inject~10 lines as a permanent system message in history
Investigate a specific past message/channel run <seq>full agent run
Auto-injection is silent and continuous โ€” as long as the ring has content. /channel inject is an explicit action that adds a system message to history (sticks until the next compaction). /channel run is the โ€œinvestigate nowโ€ path without a registered rule.

Reconnection and fault tolerance

ScenarioBehavior
SSE server drops mid-sessionSupervisor reconnects with full-jitter exponential backoff (500ms โ†’ 30s cap). Pending RPCs wait, no extra timeout
Streamable HTTP server drops mid-sessionSame backoff strategy, but the listener (open GET) is the channel that reconnects โ€” POSTs keep working when the server comes back
Stdio server crashesonClose fires, manager marks as disconnected in /mcp status. Notifications stop because the process is dead
HTTP server returns 405 / 404 / 501 on GETServer does not support push listener. Clean stop, no retry storms โ€” log info โ€œserver does not support pushโ€
Persistence file corruptedInvalid lines are skipped individually. File remains usable
Disk full on appendWarning once, persistence disabled for that session, in-memory ring keeps running
Notification arrives during shutdownPush becomes a no-op once Close() ran โ€” no race with the file close
Server-side sessions on Streamable HTTP: when the server emits Mcp-Session-Id on initialize, the transport echoes the header on every request (including the listener GET). Reconnection preserves the session automatically.

Use cases

CI/CD

notify rule on ci-pipeline. You see failures in the banner; /channel run <seq> launches the agent to investigate with tools.

Prod alerts (Prometheus/Datadog)

confirm rule on alerts/critical with a prompt template. Each alert becomes an โ€œinvestigate?โ€ question you answer on demand.

Canary deploys

auto rule with a tight whitelist (kubectl_*, http_request). Every canary kicks off smoke checks automatically.

External webhooks (GitHub/Jira/Slack)

A custom MCP server translates webhooks into notifications. ChatCLI pulls from the ring on next turns or triggers dedicated rules.

Limits and trade-offs

ItemValueWhy
In-memory ring200 messagesCovers normal usage (several hours of CI/alerts) without bloating memory
Auto-injection5 messagesBalance between useful context and token cost per turn
/channel inject10 messagesMore depth for explicit investigation
Persistence file10 MiB before rotationEnough for ~50k (short) messages
Load on boot200 messagesKeeps the ring โ€œwarmโ€ without reading a huge file
Reconnect backoff500ms โ†’ 30s, full jitterRecovers fast from blip, avoids thundering herd
Confirm expiration30 minBounded memory; user who vanished for hours doesnโ€™t accumulate confirms
Engine action buffer64Defense against โ€œburst stormโ€ โ€” drop with warn rather than back-pressure
These numbers are not configurable today. They may be parametrized in a future release โ€” open an issue if you hit any of them.

Troubleshooting

Expected behavior if you only have notify rules or no rules at all. notify is passive by design. For action:
  • Add a confirm or auto rule to ~/.chatcli/mcp/triggers.json and run /channel rules reload.
  • Or run /channel run <seq> manually against the message.
May be a listener disabled. The Streamable HTTP listener is opt-in by spec โ€” check:
  1. /mcp logs <name> โ€” if you see server returned 405 on GET <url>, 404, or 501, the server does not implement push and the listener stopped cleanly.
  2. Otherwise, check that the server is emitting notifications in JSON-RPC format without id. Non-JSON data: content is also captured (lands in the raw channel), but doesnโ€™t trigger rules tied to specific channels.
In order:
  1. /channel rules โ€” confirm the rule exists and is active.
  2. Was /channel pause run? /channel resume reactivates.
  3. Matching: does the server/channel/contentRegex actually match? Remember that channel: "alerts" does NOT match "alerts/critical" โ€” use "alerts/*".
  4. rateLimit or dedupWindow โ€” a recent fire may be suppressing.
Yes, with the session closed: rm ~/.chatcli/mcp/channels.jsonl*. On next boot, the ring starts empty.At runtime, rotation automatically cuts at 10 MiB โ€” you can reach ~20 MiB total (active + .1) before the next cut.
/channel rules reload. If reload fails (invalid schema), the error appears in the response and ChatCLI keeps the previous rules active โ€” no โ€œno rulesโ€ state.If the error is a regex, paste it into https://regex101.com flavor โ€œGolangโ€ to isolate.
Symptom: agent investigates, does something that triggers another notification, which triggers the rule again. Fix:
  1. Significantly bump rateLimit (e.g., "15m").
  2. Refine the contentRegex to only catch the pathological case.
  3. /channel pause while you investigate the config offline.
Use /channel inject โ€” puts the last 10 as a system message in history. Persists in history until the next compaction.To view without injecting into the LLM: /channel list shows up to 20.
Check the ruleโ€™s tools whitelist. In auto, only listed tools can be invoked without extra approval; tools outside the whitelist follow the agentโ€™s normal approval flow โ€” which may be your intent or not. Refine the whitelist in triggers.json.

Next steps

MCP Integration

Configure MCP servers โ€” foundation for channels.

MCP Config

Complete reference of mcp_servers.json (including the channels field).

Hooks System

Lifecycle hooks โ€” complement channels for internal events.

Command Reference

Full slash command list, including /channel.