This guide builds a factory, @oncall, that turns an alert or a Slack message into a tested fix on a pull request within minutes, while a person still decides what merges.
What you will build
alert webhook ─┐
├─> foreman ─> triage ─> fix ─> verify (CI + tests) ─> review ─> hand-off
@oncall Slack ─┘No spec stage, because incidents need speed. People still approve risky commands and merge the fix.
1. The factory
# factory.yaml
schemaVersion: kiln/v1
name: incident-response
alias: oncall
repositories:
- { owner: acme, name: web }
integrations: [slack]
agentDefaults:
harness: { type: claude-code, model: claude-sonnet-5-5 }
runner: linux-node
defaultPipeline: incident
budgets: { perWorkItemUsd: 25 }2. The agents
agents/foreman/agent.md:
---
agentType: FOREMAN
description: Coordinates incident work
---
Keep the thread short and factual. Ask instead of guessing.agents/triage/agent.md:
---
agentType: TRIAGE
---
Classify the alert. Name the most likely cause from the runbooks skill and quote the evidence.agents/fixer/agent.md:
---
agentType: IMPLEMENT
description: Smallest safe fix with a regression test
---
Make the smallest change that stops the incident. Add a test that fails without the fix.
Do not refactor. Do not touch infrastructure or data.agents/verify/agent.md:
---
agentType: VERIFY
---
Run the test suite. Report testsPassed in the result file.agents/review/agent.md:
---
agentType: REVIEW
---
Check that the fix is minimal and the new test covers the failure.
Use needs_human for anything that touches auth, payments or data.3. Runbooks as a skill
skills/runbooks/SKILL.md:
---
name: runbooks
description: Known incident causes and how to confirm each one
owner: sre
---
## 502s from the API
Check the last deploy and the connection pool size in config/db.ts.
## Login loop
Check the session cookie domain in config/session.ts.Every agent receives it. See Skills.
4. The pipeline
# pipelines/incident.yaml
description: Fast path for incidents, no spec
stages:
- { id: triage, agent: triage }
- { id: build, agent: fixer }
- { id: verify, agent: verify, requires: [ci_green, tests_pass], on_fail: build, max_loops: 2 }
- { id: review, agent: review, on_fail: build, max_loops: 1 }
- { id: handoff, action: handoff }After two failed verify loops, Kiln opens a verify-stuck question in the thread and parks the item, so a person takes over. See Pipelines and gates.
5. A runner with a closed network
# runners/linux-node.yaml
backend: docker
shape: { vcpus: 2, memoryGb: 4 }
setupCommands:
- corepack enable
- pnpm install --frozen-lockfile
egress:
- registry.npmjs.org
- llm-gateway.internal.acme.comOnly these hosts and the hosts of the cloned repositories, here github.com, are reachable. See Sandboxes.
6. A stricter command guard
Incidents tempt people to touch production. Block anything that looks like it:
# decisions/command-guard.yaml
state: [command, cwd, repo, branch]
questions:
risk_level: { type: choice, options: [safe, risky, destructive] }
remote_exec: { type: probability }
deletes_files: { type: probability }
touches_prod: { type: probability }
policy:
block: "risk_level.destructive >= 0.6 or remote_exec >= 0.6 or touches_prod >= 0.5"
ask_human: "risk_level.risky >= 0.3 or deletes_files >= 0.5"
on_error: deny
timeout_ms: 800With on_error: deny, an unanswered decision takes the first policy entry, block. See Typed decisions.
# policies/guard.yaml
protectedPaths: [decisions/**, pipelines/**]7. Alerts and Slack
# webhooks/alerts.yaml
authMode: signature
signatureScheme: standard_webhooks
secretName: ALERTS_WEBHOOK_SECRET
deliveryIdHeader: webhook-idautomations/alerts/automation.md:
---
triggers:
- provider: webhook
event: received
filter: { webhook_ids: [alerts] }
---
An alert fired. Find the cause, then fix it with a regression test.automations/slack/automation.md:
---
triggers:
- provider: slack
event: app_mention
filter: { channel: [C0INCIDENTS] }
---
Treat this as an incident report.Store ALERTS_WEBHOOK_SECRET in Kiln and point your alerting tool at POST /webhooks/oncall/alerts. The alert's title or summary field becomes the work item's title. See Automations.
One task per incident
An alarm usually arrives from more than one place: the alert webhook fires, then someone posts in Slack. Kiln keeps one task for the incident. Alerts that carry the same fingerprint, dedup_key or incident_id join the open task, and a Slack report that names the same service and error code joins it by content. Both threads then get the updates. Set deduplication: { windowHours: 6 } in factory.yaml to compare only recent tasks, or enabled: false to turn it off. See Tasks and runs.
8. Validate and apply
kiln validate .
kiln apply --dry-run
kiln apply9. Try it
Mention the factory in the incidents channel:
@oncall users get a login loop after the 14:02 deployFollow the thread. You should see a "Ready to merge" message with the pull request, which has the fix, the regression test and the factory:oncall label. If the fixer wants to run a risky command, an approval request appears in the inbox.
10. Measure it
Add a scorer for the fixer, and watch cycle time and cost per merged PR on the dashboard. See Scorers and Dashboard metrics.