一键导入
envoy
Use when subscribing sessions to Envoy topics, sending agent-to-agent messages, or reasoning about topic formats for Slack/GitHub/agent routing.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when subscribing sessions to Envoy topics, sending agent-to-agent messages, or reasoning about topic formats for Slack/GitHub/agent routing.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when coordinating Legion workers across issues, dispatching workers, monitoring progress, or routing triage items
Use when dispatched by Legion controller to work on an issue in a jj workspace
OpenCode serve HTTP API reference. Use when interacting with sessions, reading messages/todos, aborting workers, managing workspaces, or any direct OpenCode serve API call. Each `opencode serve` process is independent with its own port; Legion runs one shared serve per daemon (default port 13381, may differ if multiple daemons coexist on the host).
Capture learnings from completed work via dual-perspective retrospective. Invoked by resuming an implement worker session — the implementer has full context, and a fresh subagent provides an outside view.
Manage GitHub issues via Projects V2. Use when LEGION_ISSUE_BACKEND=github.
Research institutional knowledge before escalating questions to users. Check docs/solutions/ and codebase patterns before asking humans.
| name | envoy |
| description | Use when subscribing sessions to Envoy topics, sending agent-to-agent messages, or reasoning about topic formats for Slack/GitHub/agent routing. |
Envoy is Legion's event-routing subsystem. It delivers Slack, GitHub, and agent-to-agent events to OpenCode sessions.
envoy_subscribe(topics) — make the current session RECEIVE future events on those topicsenvoy_unsubscribe(topics?) — stop receiving some or all topicsenvoy_list() — show the session's current subscriptionsenvoy_send(target_session, message) — SEND a message directly to another sessionnotifications.agent.<session_id>Example:
notifications.agent.ses_2e6ca3034ffejVikSZ8mDwk0mRnotifications.role.<role>Role topics route to exactly one session — whoever last called envoy_role_set(role="<role>"). Claiming a role transfers it from the previous holder using ordered last-writer-wins semantics.
Examples:
notifications.role.legion-controller (routes to whoever holds the controller role)notifications.role.opencode-dev (routes to the active dev session)notifications.role.legion-po (routes to the product owner session)GitHub topics are resource-scoped — every event includes the resource type and number (or, for push/workflow events, the ref or workflow filename).
Topic structure: notifications.github.<owner>.<repo>.<resource_type>.<number>.<event_kind>
notifications.github.<owner>.<repo>.pr.<number>notifications.github.<owner>.<repo>.issue.<number>notifications.github.<owner>.<repo>.pr.<number>.commentnotifications.github.<owner>.<repo>.issue.<number>.commentnotifications.github.<owner>.<repo>.pr.<number>.reviewnotifications.github.<owner>.<repo>.pr.<number>.cicheck_run webhooks attached to a PR are aggregated ingest-side into per-commit state (JetStream KV bucket envoy_ci_state) and re-emitted as one debounced JSON summary once the commit's checks have been quiet for the debounce window (ENVOY_CI_DEBOUNCE, default 5s). A subscriber sees the checks "build up" instead of every transition. A new summary is emitted only when the check set actually changes (new push → fresh tally; a re-run flips a check back to running and re-emits).PayloadSummary is a compact JSON object (Payload unused): {"kind":"ci_summary","repo":"<o>/<r>","number":"<n>","sha":"<sha>","failed":{"count":N,"checks":[...]},"running":{...},"passed":{...},"queued":{...},"skipped":{...}}. Each status is {count, checks} with the full sorted name list (nothing collapsed); every status is always present ({"count":0,"checks":[]} when empty).check_suite is ignored (per-app rollup, no per-check name). Checks not tied to a PR are dropped — there is no repo-wide CI topic. For non-PR visibility, use the workflow.<filename>.<action> family instead. Individual GitHub Actions jobs (workflow_job webhooks) are not routed — only whole workflow runs.notifications.github.<owner>.<repo>.pr.<number>.mentionnotifications.github.<owner>.<repo>.issue.<number>.mentionnotifications.github.<owner>.<repo>.mentionnotifications.github.<owner>.<repo>.push.branch.<branch>notifications.github.<owner>.<repo>.push.tag.<tag>
Branch and tag names with dots are sanitized to underscores (v1.0.0 → v1_0_0).
Push events for refs other than refs/heads/... and refs/tags/... are not routed.notifications.github.<owner>.<repo>.workflow.<filename>.<action>
Filename is the basename of workflow_run.path with dots sanitized (ci.yml → ci_yml).
action is one of requested, in_progress, completed.Using wildcards to subscribe broadly:
notifications.github.<owner>.<repo>.>notifications.github.<owner>.<repo>.pr.>notifications.github.<owner>.<repo>.pr.<number>.>notifications.github.<owner>.<repo>.issue.>notifications.github.<owner>.<repo>.issue.<number>.>notifications.github.<owner>.<repo>.push.>notifications.github.<owner>.<repo>.push.branch.mainnotifications.github.<owner>.<repo>.push.branch.>notifications.github.<owner>.<repo>.push.tag.>notifications.github.<owner>.<repo>.workflow.>notifications.github.<owner>.<repo>.workflow.ci_yml.>notifications.github.<owner>.<repo>.workflow.*.completedExamples:
notifications.github.trajectory-labs-pbc.agent-c.pr.9880 (PR #9880 state changes)notifications.github.trajectory-labs-pbc.agent-c.pr.9880.comment (comments on PR #9880)notifications.github.trajectory-labs-pbc.agent-c.issue.9909.> (all events on issue #9909)notifications.github.sjawhar.legion.pr.> (all PR events across all PRs)notifications.github.sjawhar.legion.mention (all @mentions repo-wide)notifications.github.sjawhar.legion.push.branch.main (pushes to main)notifications.github.sjawhar.legion.workflow.ci_yml.in_progress (CI workflow starts)notifications.slack.<team_id>.<channel_id>.messagenotifications.slack.<team_id>.<channel_id>.mentionnotifications.slack.<team_id>.<channel_id>.thread.<normalized_ts>.messagenotifications.slack.<team_id>.<channel_id>.thread.<normalized_ts>.mentionThread timestamps are normalized: 1234567890.123456 → 1234567890_123456
(dots replaced with underscores to make the thread ID a single NATS segment).
Examples:
notifications.slack.T09FRELLTS8.C0A0DHVU8HE.messagenotifications.slack.T09FRELLTS8.C0A0DHVU8HE.mentionnotifications.slack.T09FRELLTS8.C0A0DHVU8HE.thread.1234567890_123456.messagenotifications.slack.T09FRELLTS8.C0A0DHVU8HE.thread.1234567890_123456.mentionnotifications.ghostwispr.<session_id>.session.startednotifications.ghostwispr.<session_id>.session.endednotifications.ghostwispr.<session_id>.summary.readyParameters:
<session_id>: Ghost Wispr session timestamp string (e.g., 20260326041405). Alphanumeric only, safe for NATS topic segments.session.started, session.ended, summary.readyExamples:
notifications.ghostwispr.20260326041405.session.ended (session ended)notifications.ghostwispr.20260326041629.summary.ready (summary ready)notifications.whatsapp.<phone>.<jid>.messagenotifications.whatsapp.<phone>.<jid>.statusParameters:
<phone>: Connected WhatsApp account phone number in E.164 digits-only format (no + prefix). Example: 15551234567. This identifies which WhatsApp account the events belong to — not the remote contact.<jid>: Remote chat's WhatsApp JID. Individual: PHONE@s.whatsapp.net. Group: ID@g.us.message, status⚠️ JID dot expansion: JID dots (
.) become additional NATS subject tokens. For example,5551234567@s.whatsapp.netsplits into tokens5551234567@s,net. This means individual chat topics produce 7 tokens and group chat topics produce 6 tokens. Always use>(multi-level wildcard), never*(single-token wildcard), when subscribing to a chat or phone number.
Examples:
notifications.whatsapp.15551234567.5551234567@s.whatsapp.net.message (individual chat messages — note this expands to 7 NATS tokens)notifications.whatsapp.15551234567.120363XXX@g.us.message (group chat messages — 6 NATS tokens)envoy_subscribe([...])envoy_list() to confirmenvoy_send(target_session, message)You do NOT need to subscribe in order to send or publish.
Don't sleep-poll. Don't "check back in N minutes." Subscribe to the event and continue with productive work — the system will wake the session when the event arrives.
notifications.github.<owner>.<repo>.pr.<num>.> for all PR events; .pr.<num>.ci for per-PR check/CI status updates; .workflow.<filename>.completed for a workflow run finishing)envoy_subscribe([...])If you have nothing else to do, end the response. The user is not your alarm clock; do not loop with sleep.
envoy_subscribe(topics) — receive future events on those topicsenvoy_unsubscribe(topics?) — stop receiving some or all topicsenvoy_list() — show current subscriptionsenvoy_send(target_session, message) — send directly to a specific session (point-to-point)envoy_publish(topic, message) — broadcast to any topic (all matching subscribers receive it)envoy_role_set(role) — claim a named role for the current session (exactly-one-holder)envoy_subscribe([
"notifications.slack.T09FRELLTS8.C0A0DHVU8HE.mention"
])
envoy_subscribe([
"notifications.slack.T09FRELLTS8.C0A0DHVU8HE.thread.1234567890_123456.>"
])
envoy_subscribe([
"notifications.slack.T09FRELLTS8.C0A0DHVU8HE.thread.1234567890_123456.message"
])
envoy_subscribe([
"notifications.slack.T09FRELLTS8.C0A0DHVU8HE.thread.>"
])
envoy_subscribe([
"notifications.github.trajectory-labs-pbc.agent-c.pr.>"
])
envoy_subscribe([
"notifications.github.trajectory-labs-pbc.agent-c.mention"
])
envoy_send(
target_session="ses_2e6ca3034ffejVikSZ8mDwk0mR",
message="Please continue the smoke test"
)
envoy_subscribe([
"notifications.whatsapp.15551234567.5551234567@s.whatsapp.net.>"
])
Use > (not *) to catch all event kinds despite JID dot expansion into multiple NATS tokens.
envoy_subscribe([
"notifications.whatsapp.15551234567.120363XXX@g.us.>"
])
envoy_subscribe([
"notifications.whatsapp.15551234567.>"
])
Catches all conversations and event kinds for the specified phone number.
When to use which:
envoy_list() firstteam_id in topics (for example T09FRELLTS8), not a workspace slug like trajectorylabsImportant: This procedure validates Envoy's NATS → listener → session delivery path using
envoy_publish. It does not test real WhatsApp message ingestion. The generic MCP bridge (packages/envoy/cmd/mcp/) can bridge real WhatsApp events, but requires production configuration. See "Current Limitations" below.Two sessions required:
envoy_publishsetssource_sessionto the publishing session's ID. The listener skips delivering broadcasts back to the sender (packages/envoy/cmd/listener/main.go). You must subscribe in one session and publish from a different session.
envoy_subscribe([
"notifications.whatsapp.15551234567.5551234567@s.whatsapp.net.>"
])
envoy_list()
Confirm notifications.whatsapp.15551234567.5551234567@s.whatsapp.net.> appears in the subscription list.
envoy_publish(
topic="notifications.whatsapp.15551234567.5551234567@s.whatsapp.net.message",
message="Synthetic WhatsApp smoke test: hello from envoy_publish"
)
Session A should receive a notification containing the text "Synthetic WhatsApp smoke test: hello from envoy_publish". This confirms:
If the notification does not arrive: Check envoy_list() in Session A to confirm the subscription is still active. Verify the topic in envoy_publish matches the subscription pattern. Ensure you are publishing from a different session than the one subscribed.
When the MCP bridge (packages/envoy/internal/mcpbridge/envelope.go) publishes a real WhatsApp event, the Envoy envelope has this structure:
{
"event_id": "<generated unique ID>",
"source": "whatsapp",
"source_event_id": "whatsapp://messages/15551234567/5551234567@s.whatsapp.net",
"topic": "notifications.whatsapp.15551234567.5551234567@s.whatsapp.net.message",
"dedupe_key": "whatsapp.<event_id value>",
"issued_at": 1712345678000,
"payload_summary": "Hello from WhatsApp",
"payload_ref": "whatsapp://messages/15551234567/5551234567@s.whatsapp.net",
"trace_id": "<generated unique ID>"
}
Field notes:
source is "whatsapp" — in contrast, envoy_publish sets source: "agent" for synthetic messagesissued_at is in milliseconds (Unix epoch ms), not secondsdedupe_key is source + "." + event_id (e.g., "whatsapp.cuid_abc123")payload_summary is the actual message text from the MCP resource read (truncated to 200 chars), or fallback "whatsapp event from <uri>" if no text contentpayload_ref and source_event_id are both the MCP resource notification URIsource_session is omitted (empty) — the MCP bridge is not an OpenCode session, so no echo-skip occursexpires_at is omitted — the bridge does not set message expirypackages/envoy/cmd/mcp/ + packages/envoy/internal/mcpbridge/) that already supports WhatsApp topic patterns (tested in packages/envoy/internal/integration/delivery_test.go). However, it is not yet configured/deployed to connect to the @sjawhar/whatsapp-mcp server in production.envoy_publish to inject test messages into NATS. It validates Envoy delivery mechanics (NATS → listener → session), not true WhatsApp end-to-end delivery.@sjawhar/whatsapp-mcp server connection details (similar to how packages/envoy/cmd/github/ and packages/envoy/cmd/slack/ are configured for their respective platforms). The bridge would then subscribe to WhatsApp MCP resource notifications and publish Envoy envelopes to NATS automatically.whatsappSubject helper), NATS topic format, generic listener routing, and MCP bridge infrastructure all work. Only the production configuration connecting the bridge to the WhatsApp MCP server is missing.