Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions docs/src/content/docs/features/notification-routing.md
Original file line number Diff line number Diff line change
@@ -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:<type>` 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
3 changes: 3 additions & 0 deletions docs/src/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand All @@ -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' },
Expand All @@ -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' },
],
},
Expand Down
4 changes: 3 additions & 1 deletion test/docs-build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

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',
Expand Down Expand Up @@ -54,6 +54,7 @@
'capability-routing',
'consult-mode',
'copilot-coding-agent',
'decision-archival',
'directives',
'enterprise-platforms',
'export-import',
Expand All @@ -67,6 +68,7 @@
'mcp',
'memory',
'model-selection',
'notification-routing',
'notifications',
'parallel-execution',
'plugins',
Expand Down Expand Up @@ -238,7 +240,7 @@
];
for (const { dir, name } of allExpected) {
const htmlPath = join(DIST_DIR, 'docs', dir, name, 'index.html');
expect(existsSync(htmlPath), `Missing: docs/${dir}/${name}/index.html`).toBe(true);

Check failure on line 243 in test/docs-build.test.ts

View workflow job for this annotation

GitHub Actions / test

test/docs-build.test.ts > Docs Build Script (Astro) > all expected doc pages produce HTML in dist/

AssertionError: Missing: docs/reference/config-model/index.html: expected false to be true // Object.is equality - Expected + Received - true + false ❯ test/docs-build.test.ts:243:79
}
});

Expand Down
Loading