Last Updated: January 10, 2026 Status: Target architecture for enterprise product Purpose: Guide implementation decisions
IncidentFox supports two agent deployment modes:
- Shared Runtime (default): All teams share agent pods, cost-effective
- Dedicated Pods (enterprise): Teams get isolated deployments with custom resources
See: /docs/MULTI_TENANT_DESIGN.md for detailed comparison, cost analysis, and provisioning procedures
┌─────────────────────────────────────────────────────────────────────────────┐
│ CONFIG SERVICE │
│ (Data Plane - CRUD) │
│ │
│ • Team/org hierarchy • Direct client access │
│ • Team configuration (prompts, tools) • Routing lookup │
│ • Tokens (issue, revoke, validate) • Audit logs (config + runs) │
│ • Effective config computation • No infrastructure ops │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ ORCHESTRATOR │
│ (Control Plane - Workflows + Webhooks) │
│ │
│ • All external webhooks • K8s resource creation │
│ • Routing lookup (calls Config Svc) • AI Pipeline scheduling │
│ • Agent run triggering • Provisioning workflows │
│ • Rate limiting • Audit (incoming events) │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ AGENT SERVICE │
│ (Data Plane - Execution) │
│ │
│ • Run agents (planner, investigation) • No webhook handling │
│ • Execute tools (K8s, AWS, etc) • No routing logic │
│ • Post results (Slack, GitHub) • Called by Orchestrator only │
└─────────────────────────────────────────────────────────────────────────────┘
Clients can call Config Service directly for all data operations:
# Team management
POST /api/v1/admin/orgs/{org}/teams/{team} # Create team
PUT /api/v1/admin/orgs/{org}/nodes/{node}/config # Update config
DELETE /api/v1/admin/orgs/{org}/teams/{team} # Delete team
# Tokens
POST /api/v1/admin/orgs/{org}/teams/{team}/tokens # Issue token
DELETE /api/v1/admin/orgs/{org}/teams/{team}/tokens/{id} # Revoke
# Runtime
GET /api/v1/config/me/effective # Get team config
POST /api/v1/internal/routing/lookup # Routing lookup
All webhooks go to Orchestrator:
POST /webhooks/slack/events # Slack @mentions
POST /webhooks/slack/interactions # Slack buttons
POST /webhooks/github # GitHub comments, CI failures
POST /webhooks/pagerduty # PagerDuty alerts
POST /webhooks/incidentio # Incident.io incidents
POST /webhooks/generic # Custom webhooks
K8s resources, multi-service coordination:
POST /api/v1/admin/provision/team # Full provisioning (config + K8s)
DELETE /api/v1/admin/provision/team # Deprovisioning + cleanup
POST /api/v1/admin/schedules/team # Create pipeline CronJob
POST /api/v1/admin/agents/run # Admin-triggered agent run
1. External Source → Orchestrator (webhook endpoint)
2. Orchestrator: Verify signature
3. Orchestrator: Rate limit check (TODO)
4. Orchestrator → Source: Return 200 OK (async processing)
5. Orchestrator → Config Service: Routing lookup
6. Orchestrator: Log incoming event (audit)
7. Orchestrator → Agent: Run agent with team context + slack_context
8. Agent: Post initial "working" message to Slack (Block Kit)
9. Agent: Execute investigation, use tools
10. Agent: Update Slack with progress (real-time)
11. Agent: Post final results to Slack (rich Block Kit)
12. Orchestrator → Config Service: Save agent run audit
The Agent now posts results directly to Slack instead of returning to Orchestrator:
Orchestrator Agent Slack
│ │ │
│ POST /agents/planner/run │ │
│ + slack_context: { │ │
│ channel_id, thread_ts, │ │
│ user_id │ │
│ } │ │
│─────────────────────────────────▶ │ │
│ │ chat.postMessage (initial) │
│ │─────────────────────────────────▶│
│ │ │
│ │ (runs tools, updates progress) │
│ │ │
│ │ chat.update (progress) │
│ │─────────────────────────────────▶│
│ │ │
│ │ chat.update (final result) │
│ │─────────────────────────────────▶│
│ │ │
│ {"success": true, │ │
│ "output_mode": "slack_direct"} │ │
│◀───────────────────────────────── │ │
Why Agent-Direct?
- Real-time progress updates as phases complete
- Rich Block Kit UI with structured output
- Agent already has the rendering logic (
slack_ui.py,slack_output.py) - Single responsibility: Orchestrator routes, Agent outputs
1. Web UI → Config Service: PUT /api/v1/admin/orgs/{org}/nodes/{node}/config
2. Config Service: Validate, save, audit
3. Config Service → Web UI: Return updated config
1. Web UI → Orchestrator: POST /api/v1/admin/provision/team
2. Orchestrator → Config Service: Create team node
3. Orchestrator → Config Service: Set routing config
4. Orchestrator → Config Service: Issue team token
5. Orchestrator → K8s API: Create CronJob for AI Pipeline
6. Orchestrator → AI Pipeline: Trigger bootstrap
7. Orchestrator: Record provisioning run (audit)
8. Orchestrator → Web UI: Return success + token
- Add webhook endpoints to Orchestrator:
-
/webhooks/slack/events- Full signature verification -
/webhooks/slack/interactions- Signature verification -
/webhooks/github- X-Hub-Signature-256 verification -
/webhooks/pagerduty- X-PagerDuty-Signature verification -
/webhooks/incidentio- X-Incident-Signature verification
-
- Port signature verification logic from Agent/Web UI (
webhooks/signatures.py) - Add routing lookup (call Config Service
/api/v1/internal/routing/lookup) - Add audit logging for incoming events
- TODO: Test with real webhooks (point test channel to new endpoints)
- TODO: Gradually migrate external webhook URLs to Orchestrator
- Mark Agent webhook endpoints as deprecated (log warnings)
- Mark Web UI webhook endpoints as deprecated
- Updated Slack trigger to use Config Service routing (with fallback)
- TODO: After 1 release cycle, remove deprecated endpoints
- TODO: Remove
orchestrator_team_slack_channelstable (migration)
- Add K8s client to Orchestrator (
kubernetes>=29.0.0in pyproject.toml) - Implement CronJob creation (
k8s/cronjobs.py) - Implement CronJob deletion
- Integrate CronJob creation into provisioning endpoint
- TODO: Add
/api/v1/admin/schedules/teamendpoint for manual schedule updates
- Add
deployment_modefield to ProvisionRequest - Implement K8s Deployment creation (
k8s/deployments.py) - Implement K8s Service creation
- Update all webhook handlers to use dedicated agent URL when configured
- Store
dedicated_service_urlin team config after provisioning - TODO: Implement dedicated pod cleanup during deprovisioning
- TODO: Add resource limits configuration UI
- Agent receives team context from Orchestrator (via X-IncidentFox-Team-Token)
- Keep existing endpoints working (backwards compatibility)
- Agent posts results directly to Slack via
slack_contextparameter - New file:
agent/src/ai_agent/core/slack_output.py- Generalized Slack Block Kit output - TODO: Add clean
/api/v1/runendpoint (standardized interface)
- Removed
_post_slack_result()from Orchestrator (TD-001) - Removed deprecated
/api/v1/internal/slack/triggerendpoint (TD-002) - Removed Agent webhook endpoints (TD-003) - file reduced 2461 → 838 lines
- Removed Web UI
/api/slack/events/route.ts(TD-004) - Removed Web UI
/api/github/webhook/route.ts - Removed Web UI
/api/pagerduty/webhook/route.ts - Added migration
003_drop_team_slack_channelsto drop table (TD-005) - Removed
TeamSlackChannelmodel frommodels.py
Completed: 2026-01-10
| Secret | Stored In | Accessed By |
|---|---|---|
| Slack signing secret | Orchestrator | Orchestrator |
| Slack bot token | Orchestrator + Agent | Orchestrator (webhook ack), Agent (post results) |
| GitHub webhook secret | Orchestrator | Orchestrator |
| GitHub token | Agent | Agent (post comments, reactions) |
| PagerDuty webhook secret | Orchestrator | Orchestrator |
| Incident.io webhook secret | Orchestrator | Orchestrator |
| OpenAI API key | Agent | Agent |
| Database URL | Config Service, Orchestrator | Both |
| K8s API access | Orchestrator | Orchestrator (CronJobs, Deployments) |
| Endpoint | Auth Method |
|---|---|
| Config Service admin endpoints | Admin token (org-scoped) |
| Config Service team endpoints | Team token |
| Config Service internal endpoints | X-Internal-Service header |
| Orchestrator admin endpoints | Admin token (via Config Service) |
| Orchestrator webhooks | Source-specific signature |
Agent /api/v1/run |
Team token (from Orchestrator) |
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /api/v1/admin/orgs/{org}/teams/{team} |
Admin | Create team |
| PUT | /api/v1/admin/orgs/{org}/nodes/{node}/config |
Admin | Update config |
| POST | /api/v1/admin/orgs/{org}/teams/{team}/tokens |
Admin | Issue token |
| GET | /api/v1/config/me/effective |
Team | Get effective config |
| POST | /api/v1/internal/routing/lookup |
Internal | Routing lookup |
| POST | /api/v1/internal/agent-runs |
Internal | Record agent run |
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /webhooks/slack/events |
Slack signature | Slack @mentions |
| POST | /webhooks/github |
GitHub signature | GitHub comments |
| POST | /webhooks/pagerduty |
PagerDuty signature | PagerDuty alerts |
| POST | /webhooks/incidentio |
Incident.io signature | Incidents |
| POST | /api/v1/admin/provision/team |
Admin | Full provisioning |
| DELETE | /api/v1/admin/provision/team |
Admin | Deprovisioning |
| POST | /api/v1/admin/agents/run |
Admin | Admin agent run |
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /api/v1/run |
Team token | Run agent |
| GET | /health |
None | Health check |
| GET | /metrics |
None | Prometheus metrics |
- Don't duplicate routing storage - Config Service is single source of truth
- Don't handle webhooks in multiple places - Orchestrator only
- Don't call Agent directly from external sources - Always through Orchestrator
- Don't store team config in Orchestrator - That's Config Service's job
- Don't give Agent K8s API access - That's Orchestrator's job
- Don't give Config Service K8s API access - Data plane only
- Don't break existing endpoints - Add new, deprecate old, then remove
This architecture is successful when:
- ✅ All webhooks have single entry point (Orchestrator)
- ✅ All team data in single place (Config Service)
- ✅ K8s operations in single place (Orchestrator)
- ✅ Agent is stateless executor (no routing, no webhooks)
- ✅ Clear audit trail for all events
- ✅ Web UI only calls Config Service for CRUD
- ✅ No duplicate data storage