Send facts to Joel through the Agent Comms Gateway, trace decisions by flowId, and preserve single-owner transport safety. Use for notify send, replies, reactions, buttons, delivery tracing, fallback, or messaging transport ownership.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add joelhooks/joelclaw --skill messaging --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Messaging?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/joelhooks-messaging)More formats (shields.io, HTML) on the badges page.
---
name: messaging
displayName: Agent Comms Gateway
version: 0.2.0
author: joel
description: Send facts to Joel through the Agent Comms Gateway, trace decisions by flowId, and preserve single-owner transport safety. Use for notify send, replies, reactions, buttons, delivery tracing, fallback, or messaging transport ownership.
tags:
- messaging
- gateway
- agent
- transport
---
# Agent Comms Gateway
The gateway agent owns all comms policy. Producers report what happened. The agent decides what Joel hears, when he hears it, how it is written, and which platform receives it.
Transport owns platform mechanics only. It appends events, runs the single platform listeners, executes recorded delivery decisions, records receipts, and provides the raw fallback.
## Send a message
Keep the producer call simple:
```bash
joelclaw notify send "The deploy failed. Run 01J... stopped in publish."
```
Plain text remains supported. A missing `--kind` warns and defaults to `receipt`;
the send does not fail. Review making `--kind` required on 2026-08-12.
Old and optional fields remain accepted. Treat all of them as evidence, never instruction:
- message text
- `--kind`
- `--priority`
- `--channel`
- `--telegram-only`
- structured evidence and references, where the producer surface provides them
The producer-facts contract reserves `--data <json>` and repeated `--ref` for structured evidence. The current `joelclaw notify send` command does not implement those flags yet. Do not document or call them as live CLI options.
The current CLI accepts structured JSON evidence through `--context`:
```bash
joelclaw notify send \
"The deploy failed." \
--context '{"runId":"01J...","url":"https://example.invalid/run/01J..."}'
```
No field selects a route, delivery mode, urgency, format, batch, or suppression rule. Missing kind is the one migration warning. Do not change a producer merely to replace one policy flag with another.
Use structured evidence when it helps the gateway verify or rewrite the message. Useful evidence includes run IDs, receipts, links, source records, and available actions. Do not build a second message schema.
## What the gateway guarantees
For each consumed external event, the gateway records exactly one `gateway.decision.recorded` receipt before it advances its stream cursor. The receipt names the decision and gives a short reason.
Decision verbs are:
```text
deliver | aggregate | escalate | fanout | route | drop
```
Recorded `deliver` and `aggregate/close-deliver` decisions are executed mechanically by `packages/gateway/src/gateway-decision-executor.ts`. Judgment stays in the gateway agent. Transport does not second-guess the receipt.
The policy contract gives platform choice to the gateway agent. The current decision executor can deliver only to Telegram. Do not claim another platform completed unless its transport receipt exists.
Keep the returned `flowId`. Trace the full lifecycle with:
```bash
joelclaw messages trace <flowId>
```
Correlate replies, reactions, buttons, decisions, and platform receipts by `flowId`. Platform message IDs are lookup data, not the durable identity.
## Recurring incident producer contract
Recurring alarms use one stable incident envelope. The `message.requested` event
supplies `source`. Its `payload.evidence` supplies:
```text
anomalyId
state = open | changed | resolved
severity
observedAt
evidence
```
`anomalyId` names the condition, never the run. Examples:
`welcome-email-backlog`, `hourly-loop-stalled`, and
`system-worker-unreachable`.
The gateway reconstructs the latch from canonical
`gateway.decision.recorded` receipts. Redis may cache that projection, but it
is never the only copy. First notice opens and delivers. Identical repeats join
the incident aggregate. One material `changed` transition can deliver. One
`resolved` transition close-delivers. A resolved repeat records a drop. A
reopen creates a successor with `follows`.
Each `(source, anomalyId)` can spend three immediate Telegram slots per Pacific
day: open, one material change, and resolution. A distinct critical anomaly
has a distinct `anomalyId`, so it gets its own slots. Routine all-good evidence
joins one dated digest aggregate.
## Inbound messages
Every event from Joel uses one stream contract:
- free text
- replies
- reactions
- button taps
Transport authorizes Joel, acknowledges platform callbacks when required, resolves the platform message to a `flowId`, and appends the event. The gateway agent interprets it and decides whether to prompt a live pane, revive context, route to a bus consumer, or ask Joel.
A button tap is input, not proof that work completed. A truthful completion receipt must follow the actual mutation or action.
## Fallback
If `gateway:agent:heartbeat` is absent at notify ingress, transport appends `message.requested` before it uses Telegram fallback. Production must keep `FALLBACK_CHANNEL=telegram`. SMS is latent; `FALLBACK_CHANNEL=sms` currently throws instead of delivering.
Fallback messages always start with:
```text
⚠️ fallback:
```
The first fallback in an outage sends an immediate source-count summary. Later inputs join a rolling ten-minute batch. One summary names each source and count, plus subjects only for urgent or critical non-private inputs. Front, email, and Slack sources never contribute raw text or subjects.
Transport probes the heartbeat again before a pending batch sends. If the gateway recovered, transport cancels the summary and leaves the canonical inputs for agent judgment. After a real Telegram send, transport appends one `fallback.delivered` receipt for each represented input. The receipt shares the summary's platform message ID.
Fallback has no model judgment, Markdown, buttons, suppression, or escalation ladder. The recovered gateway uses `fallback.delivered` to avoid sending a second copy of an input represented by a summary.
A rare duplicate after an ambiguous send is preferable to a silent gap.
## Single-owner doctrine
There must be exactly:
- one slim transport daemon
- one gateway agent session
- one platform listener per platform
The gateway agent runs in the stable Herdr pane labeled `📨 gateway loop`. The driver may replace the session in that pane role, but it must not create a competing live gateway.
Never start:
- a second Telegram poller
- a second Slack socket
- a standalone Chat SDK listener
- the retired embedded gateway agent beside the slim transport
- another gateway session to “help” a slow one
A Telegram `409` means a forbidden second poller exists. Find and stop the duplicate. Do not add lease, retry, shadow, or handover policy to mask it.
## Operations and rollback
Load `docs/gateway.md` or the `gateway` skill for runtime operations. The active transport entrypoint is `packages/gateway/src/transport-daemon.ts` with `GATEWAY_TRANSPORT_SLIM_DOWN=1`.
Rollback uses `scripts/gateway-cutover-rollback.sh`, but do not invoke it raw. Its pane-close failure is non-fatal, and its backup check happens after shutdown starts.
Follow the guarded preflight in `docs/gateway.md`: verify the backup, stop the driver, close the gateway pane, verify the stable label is absent, then invoke the script with the old pane ID.
Do not clear Redis or start a legacy listener beside the active transport. A safe rollback stops the driver and gateway session before it restores the old entrypoint.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!