showcase · tier 4 · ops & support
Incident war room
Logs, status history and the runbook gathered in parallel, a typed timeline, and a recovery check before any draft.
The whole file
10 tasks · 8 waves · the plan falls out of the bindings, nothing is scheduled by hand.
t4-incident-war-room.nika.yamlsource
nika: v1workflow: id: incident-war-room description: "parallel evidence → typed timeline → settle + recheck → postmortem draft"model: ollama/qwen3.5:4b # local default · the synthesis task below overrides to a stronger modelvars: service: "checkout-api" status_url: "https://status.internal.example.com/v1/services/checkout-api" log_window: "90 minutes ago"secrets: oncall_webhook: source: env key: ONCALL_WEBHOOK_URL egress: # sanction the on_finally ping · the secret IS the URL - to: "nika:notify" host_from_self: truetasks: # ── the gather wave · all three run in parallel ── logs: exec: command: ["journalctl", "-u", "${{ vars.service }}", "--since", "${{ vars.log_window }}", "--no-pager"] capture: structured status_history: invoke: tool: "nika:fetch" args: url: "${{ vars.status_url }}" mode: jq jq: ".history" retry: max_attempts: 4 backoff_strategy: exponential jitter: true runbook: invoke: tool: "nika:read" args: { path: "./runbooks/${{ vars.service }}.md" } # ── reconstruct · typed timeline ── timeline: with: # three value edges · the gather wave feeds the synthesis logs: ${{ tasks.logs.output.stdout }} history: ${{ tasks.status_history.output }} runbook: ${{ tasks.runbook.output }} infer: prompt: | Service logs · ${{ with.logs }} Status history · ${{ with.history }} Runbook · ${{ with.runbook }} Reconstruct the incident timeline as events. schema: type: object required: [events] properties: events: type: array items: type: object required: [at, what] properties: at: { type: string } what: { type: string } evidence: { type: string } # ── settle, then confirm recovery before claiming it ── settle: after: timeline: succeeded invoke: tool: "nika:wait" args: { duration: "60s" } recheck: after: settle: succeeded invoke: tool: "nika:fetch" args: url: "${{ vars.status_url }}" mode: jq jq: ".current.state" confirmed: with: recovered: ${{ tasks.recheck.output == 'operational' }} # the whole check crosses as ONE boundary expression invoke: tool: "nika:assert" args: condition: "${{ with.recovered }}" message: "Service is NOT back to operational: postmortem draft blocked" # ── the draft · only after recovery is proven ── postmortem: with: events: ${{ tasks.timeline.output.events }} after: confirmed: succeeded # state, no data · recovery proven or no draft infer: model: anthropic/claude-sonnet-4-6 # per-task override · thinking budget prompt: | Timeline · ${{ with.events }} Write the postmortem draft · summary · impact · root-cause hypotheses · 3 follow-up actions with owners left blank. thinking: enabled: true budget_tokens: 6000 save: with: postmortem: ${{ tasks.postmortem.output }} invoke: tool: "nika:write" args: path: "./incidents/${{ vars.service }}-postmortem.md" content: "${{ with.postmortem }}" create_dirs: true # the always-pattern · the on-call ping fires on EVERY outcome: # including the designed failure path (recovery NOT confirmed → the # assert fails → save is cancelled → this still runs · `after: # terminal` admits all four settled states · 03 §gate algebra). ping: after: save: terminal with: outcome: ${{ tasks.save.status }} # observe WHAT happened · same pass-set as the after edge invoke: tool: "nika:emit" args: event_type: "incident.postmortem.drafted" payload: service: "${{ vars.service }}" status: "${{ with.outcome }}" on_finally: - invoke: tool: "nika:notify" args: channel: webhook target: "${{ secrets.oncall_webhook }}" message: "Postmortem draft run finished for ${{ vars.service }} · ${{ with.outcome }}" severity: infooutputs: events: value: ${{ tasks.timeline.output.events }} description: "The reconstructed, typed incident timeline" postmortem: ${{ tasks.postmortem.output }}nika inspect · engine 0.105.0 · vendored graph, never re-derived
11 declared edges · 8 waves · the plan falls out of the bindings