diff --git a/docs/src/content/docs/features/notification-routing.md b/docs/src/content/docs/features/notification-routing.md new file mode 100644 index 000000000..1b0a57696 --- /dev/null +++ b/docs/src/content/docs/features/notification-routing.md @@ -0,0 +1,141 @@ +# Notification routing + +> ⚠️ **Experimental** — Squad is alpha software. APIs, commands, and behavior may change between releases. + +**Try this to route alerts by type:** +``` +Route security alerts to the security channel and daily digests to the digest channel +``` + +**Try this to set up channel routing:** +``` +Configure notification routing so each notification type goes to its own channel +``` + +When your squad grows, notifications flood a single channel. Failure alerts drown in daily briefings, tech news buries security findings, and everything gets ignored. The notification-routing skill fixes this with **topic-based routing** — agents tag notifications with a channel type, and a routing function sends them to the right destination. + +--- + +## When you need this + +You're hitting one or more of these symptoms: + +- Important alerts get missed because they're buried in routine notifications +- Team members turn off notifications entirely (signal overwhelm) +- New team members ask "where do I look for X?" + +If all your notifications land in one channel, you have a routing problem. + +--- + +## How it works + +Notification routing uses a pub-sub pattern with three components: + +| Component | Purpose | Location | +|-----------|---------|----------| +| **Channel config** | Maps notification types to channel identifiers | `.squad/teams-channels.json` | +| **CHANNEL: tag** | Agents prefix output with the target channel type | Agent output | +| **Routing dispatcher** | Reads the tag, looks up the channel, and sends the message | `.squad/notify-adapter.sh` | + +The routing config and `CHANNEL:` tags stay the same across deployments. Only the adapter changes per platform (Teams, Slack, Discord, webhook). + +--- + +## Set up channel routing + +### 1. Define your channel map + +Create `.squad/teams-channels.json` to map notification types to channels: + +```json +{ + "teamId": "your-team-id", + "channels": { + "notifications": "squad-alerts", + "tech-news": "tech-news", + "security": "security-findings", + "releases": "release-announcements", + "daily-digest": "daily-digest" + } +} +``` + +This file lives in `.squad/` so it's git-tracked and shared across the team. + +### 2. Use channel IDs for platforms that need them + +For platforms like Teams or Slack that use opaque channel IDs, store the resolved ID alongside the display name: + +```json +{ + "channels": { + "notifications": { "name": "squad-alerts", "id": "channel-id-opaque-string" }, + "security": { "name": "security-findings", "id": "channel-id-opaque-string" } + } +} +``` + +Resolve channel IDs once at setup. Use IDs at runtime — never match on display names. + +### 3. Tag notifications with CHANNEL: + +Agents prefix their output with `CHANNEL:` to signal where the notification goes: + +``` +CHANNEL:security +Worf found 3 new CVEs in dependency scan: lodash@4.17.15, minimist@1.2.5 +``` + +If no `CHANNEL:` tag is present, the dispatcher routes to the default `notifications` channel. + +### 4. Wire the routing dispatcher + +The dispatcher reads the `CHANNEL:` tag, looks up the target in your channel config, and sends the message through your platform adapter: + +```bash +dispatch_notification() { + local raw_output="$1" + local channel="notifications" # default + + if echo "$raw_output" | grep -qE '^CHANNEL:[a-z][a-z0-9-]*'; then + channel=$(echo "$raw_output" | head -1 | cut -d: -f2) + raw_output=$(echo "$raw_output" | tail -n +2) + fi + + send_notification --channel "$channel" --message "$raw_output" +} +``` + +### 5. Swap the platform adapter + +The routing layer is provider-agnostic. Point your adapter at whatever platform you use: + +``` +.squad/notify-adapter.sh # Teams / Slack / Discord / webhook — swappable +``` + +--- + +## Anti-patterns + +Avoid these common mistakes: + +| Anti-pattern | Why it fails | Fix | +|--------------|-------------|-----| +| Send all types to one channel | Everything competes for attention — alerts get buried | Route each type to its own channel | +| Use display names as identifiers | Name collisions across teams or renames break routing | Resolve channel IDs at setup, use IDs at runtime | + +--- + +## The distributed systems angle + +This is **pub-sub with topic routing** — the same principle as Kafka topics, RabbitMQ routing keys, and AWS SNS topic filtering. Each notification type is a topic. Each channel subscribes to the topics it cares about. Route by type, not by volume. + +--- + +## See also + +- [Notifications](./notifications.md) — set up basic notification delivery +- [Skills](./skills.md) — how skills encode reusable team knowledge +- [Distributed mesh](./distributed-mesh.md) — pub-sub patterns at the infrastructure level diff --git a/docs/src/navigation.ts b/docs/src/navigation.ts index 76bfc212e..8f325304e 100644 --- a/docs/src/navigation.ts +++ b/docs/src/navigation.ts @@ -46,6 +46,7 @@ export const NAV_SECTIONS: NavSection[] = [ { title: 'Response Modes', slug: 'features/response-modes' }, { title: 'Parallel Execution', slug: 'features/parallel-execution' }, { title: 'Memory', slug: 'features/memory' }, + { title: 'Decision Archival', slug: 'features/decision-archival' }, { title: 'Skills', slug: 'features/skills' }, { title: 'Directives', slug: 'features/directives' }, { title: 'Ceremonies', slug: 'features/ceremonies' }, @@ -67,6 +68,7 @@ export const NAV_SECTIONS: NavSection[] = [ { title: 'Marketplace', slug: 'features/marketplace' }, { title: 'Plugins', slug: 'features/plugins' }, { title: 'MCP', slug: 'features/mcp' }, + { title: 'Notification Routing', slug: 'features/notification-routing' }, { title: 'Notifications', slug: 'features/notifications' }, { title: 'Enterprise Platforms', slug: 'features/enterprise-platforms' }, { title: 'Squad RC', slug: 'features/squad-rc' }, @@ -87,6 +89,7 @@ export const NAV_SECTIONS: NavSection[] = [ { title: 'SDK Integration', slug: 'reference/integration' }, { title: 'Tools & Hooks', slug: 'reference/tools-and-hooks' }, { title: 'Config', slug: 'reference/config' }, + { title: 'Config Model', slug: 'reference/config-model' }, { title: 'Glossary', slug: 'reference/glossary' }, ], }, diff --git a/test/docs-build.test.ts b/test/docs-build.test.ts index aeb51d790..e3325efe9 100644 --- a/test/docs-build.test.ts +++ b/test/docs-build.test.ts @@ -19,7 +19,7 @@ const EXPECTED_GET_STARTED = ['installation', 'first-session', 'five-minute-star const EXPECTED_GUIDES = ['build-autonomous-agent', 'building-extensions', 'building-resilient-agents', 'contributing', 'contributors', 'extensibility', 'faq', 'github-auth-setup', 'personal-squad', 'sample-prompts', 'shell', 'tips-and-tricks']; -const EXPECTED_REFERENCE = ['cli', 'sdk', 'config', 'api-reference', 'integration', 'tools-and-hooks', 'glossary']; +const EXPECTED_REFERENCE = ['cli', 'sdk', 'config', 'config-model', 'api-reference', 'integration', 'tools-and-hooks', 'glossary']; const EXPECTED_SCENARIOS = [ 'aspire-dashboard', @@ -54,6 +54,7 @@ const EXPECTED_FEATURES = [ 'capability-routing', 'consult-mode', 'copilot-coding-agent', + 'decision-archival', 'directives', 'enterprise-platforms', 'export-import', @@ -67,6 +68,7 @@ const EXPECTED_FEATURES = [ 'mcp', 'memory', 'model-selection', + 'notification-routing', 'notifications', 'parallel-execution', 'plugins',