Slack ops bot
Production reference architecture for an internal Slack bot fronting ~48 ops tools. Demonstrates how routing, the context firewall, and persistent facts compose end-to-end around a realistic multi-turn investigation.
TL;DR
| What | Where |
|---|---|
| The script | examples/architectures/slack_ops_bot/main.py |
| The catalog | examples/architectures/slack_ops_bot/catalog.yaml |
| Captured output | examples/architectures/slack_ops_bot/OUTPUT.md |
| Local README | examples/architectures/slack_ops_bot/README.md |
Run it:
python examples/architectures/slack_ops_bot/main.py
(Or make architectures / make example.)
The shape
The bot walks a six-turn on-call investigation:
- "look up the on-call engineer for api-gateway" —
oncall.lookup - "tail the last hour of api-gateway logs" —
logs.tail(large output → firewall) - "show api-gateway deploy status" —
deploy.status - "roll back the api-gateway deploy to the previous build" —
deploy.rollback - "create a new incident ticket for this api-gateway outage" —
tickets.create - "show me the on-call schedule for tomorrow" —
oncall.schedule
For each turn:
- The
Routernarrows 48 tools to a top-3 shortlist (top_k=3). - The bot picks one tool from the shortlist using an explicit intent map. That separation is the load-bearing pattern: contextweaver bounds the choice, the bot (or, in production, an LLM) makes the final selection.
- The tool is "called" against a mocked backend. Large outputs go through the firewall — the 34 KB log dump in turn 2 becomes a 501-char summary on the prompt while the raw bytes are parked in the artifact store.
- Persistent facts (who's on-call, what just rolled
back, the new ticket number) are written via
ContextManager.add_fact_syncso they survive into the answer-phase prompt for every subsequent turn.
What is and isn't load-bearing
| contextweaver feature | Used | What it does here |
|---|---|---|
Router.route(query) |
✅ | 48 tools → top-3 shortlist |
| Bounded choice pattern | ✅ | Bot picks from the shortlist, not from the whole catalog |
TreeBuilder DAG |
✅ | One-shot graph build at startup; sub-millisecond routes |
| Context firewall | ✅ | 34 KB log dump → 501-char summary before it touches the prompt |
ArtifactStore |
✅ | Raw log bytes parked out-of-band for later drilldown |
FactStore |
✅ | On-call engineer + incident ticket survive across turns |
Phase-specific budgets |
✅ | Route (1.5K), answer (4K) tight enough that the budget matters |
| Dependency closure | ✅ | Tool calls + tool results stay paired as select_and_pack evicts items |
| Drilldown | ❌ | See cookbook recipe 4 |
| Async build | ❌ | Slack latency is forgiving; the voice-agent architecture exercises this |
| Alternative scorer backends | ❌ | Default TF-IDF works for this catalog; try bm25 / fuzzy on noisier ones |
| Sensitivity enforcement | ❌ | Production bots should set ContextPolicy.sensitivity_floor and tag PII |
How to adapt it
- Replace
_TOOL_RESPONSESwith calls to your real ops backends. - Replace
_select_from_shortlistwith an LLM call that picks fromRouter.route(...).candidate_ids. - Tighten
ContextBudgetonce you measure your prompt sizes. - Add a
RedactionHookif any real tool output carries PII or secrets. - Persist
mgr.fact_storeto a durable backend (the in-memory store is per-run).
Follow-ups
The other two reference architectures from issue #198 are tracked as follow-ups:
- Code-review bot — firewall on diff / grep outputs, bounded routing over a fixed set of analysis tools, latency-sensitive answer phase.
- Real-time voice agent — Pipecat-backed, tight answer-phase budgets,
asyncio.to_thread(mgr.build_sync, …)pattern.