Skip to content
Merged
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
16 changes: 16 additions & 0 deletions kits/incident-copilot/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Incident Copilot — flow IDs and Lamatic project credentials.
# Copy this to the app: kits/incident-copilot/apps/.env.example -> apps/.env.local
# NEVER commit a real .env / .env.local — only this .env.example with placeholders.

# Deployed Lamatic flow IDs (Studio -> open flow -> copy Flow ID)
INVESTIGATE_FLOW_ID=
DRAFT_COMMS_FLOW_ID=

# Lamatic project credentials (Studio -> Settings -> API)
LAMATIC_API_URL=
LAMATIC_PROJECT_ID=
LAMATIC_API_KEY=

# Optional: raises GitHub API rate limits and enables private repos.
# A fine-grained token with read-only "Contents" access is enough.
GITHUB_TOKEN=
12 changes: 12 additions & 0 deletions kits/incident-copilot/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
node_modules/
.next/
.env
.env.local
.env*.local
.DS_Store
*.log

# The repo-root .gitignore ignores any "scripts" dir; re-include this kit's
# Lamatic code-node scripts, which must ship in the PR payload.
!scripts/
!scripts/**
140 changes: 140 additions & 0 deletions kits/incident-copilot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Incident Copilot

An **investigation agent** for on-call engineers. Paste a production alert and it does what a good teammate does in the first ten minutes of an incident: reads the runbooks, checks what shipped recently, and comes back with **ranked, evidence-grounded hypotheses** — each with the evidence *for* and *against* it, and a concrete next step. Feed it new information and it **revises the ranking** instead of starting over. When you're ready, it drafts the Slack update and a postmortem skeleton.

It investigates. It does not act — no deploys, no restarts, no posting. Drafts only.

> This is the companion to [`llm-eval-harness`](../llm-eval-harness): that kit gates **generation** quality, this one helps you **diagnose** a live incident.

---

## Why this is different from a chatbot

A chatbot answers the question you asked. Incident Copilot **gathers evidence, weighs it, and argues against itself** — the three things that make it an agent rather than a prompt:

1. **It grounds every hypothesis in real evidence** — your runbooks and the actual recent commits on the affected repo (a live tool call), never a guess.
2. **It surfaces contradicting evidence, not just supporting** — the constitution makes "argue both sides" a hard rule, so you get a calibrated ranking, not a confident single answer.
3. **It gets smarter with new information** — memory is scoped to the incident ID, so a follow-up (“the DB pool is maxed”) *revises* the existing ranking and tells you what changed.

---

## Architecture

```
Next.js app (apps/) Lamatic flows
───────────────── ─────────────
paste alert + repo ─┐
│ investigate(alertText, incidentId, repoUrl)
actions/orchestrate.ts ───────────────▶ flow: investigate
▲ ┌──────────────────────────────┐
│ │ trigger (alertText, │
│ │ incidentId, repoUrl?) │
│ ┌──────┼──▶ Load_Runbooks (code) ─┐ │
│ │ │ Parse_Repo (code) │ │
│ ranked │ │ └▶ Fetch_Commits (API)│ │
│ hypotheses │ │ └▶ Shape_Changes ─┤ │
│ + evidence │ │ Retrieve_Prior (memory)─┤ │
│ │ │ (fan-in) ▼ │
│ │ │ Diagnose (Instructor │
│ │ │ LLM, JSON schema) │
│ │ │ ▼ │
│ │ │ Remember (memory add) │
│ │ └──────────────────────────────┘
│ draftComms(hypothesis, evidence, rankedHypotheses, incidentId)
actions/orchestrate.ts ───────────────▶ flow: draft-comms
Comment thread
coderabbitai[bot] marked this conversation as resolved.
┌──────────────────────────────┐
Slack update ◀─────────────────────── │ Draft_Slack (LLM, hedged) │
Postmortem ◀─────────────────────── │ Draft_Postmortem (LLM) │
└──────────────────────────────┘
```

**AgentKit capabilities used — each doing real work, none decorative:**

| Capability | Where | Why it earns its place |
|---|---|---|
| **Tool calling** | `Fetch_Commits` (`apiNode` → GitHub) | "Check recent deploys" becomes a real check, not a claim |
| **Memory** | `Retrieve_Prior` / `Remember`, keyed by incident ID | The one thing that makes re-investigation *revise* rather than repeat |
| **Structured output** | `Diagnose` (`InstructorLLMNode` + JSON schema) | Ranked hypotheses the UI can render, with guaranteed shape |
| **Multiple flows** | `investigate` + `draft-comms` | Separates "figure out what's true" from "write it up" |
| **Prompts / model-configs / constitution** | throughout | Diagnose runs at temp 0 (repeatable triage); drafts run warmer; the constitution enforces evidence + hedging |
| **Code nodes** | `Load_Runbooks`, `Parse_Repo`, `Shape_Changes` | Supply runbook grounding and shape the GitHub tool output; graceful degradation lives here |

> **Design note — why no vector store:** the runbook corpus is small (8 entries), so it's
> passed to the diagnosis model directly rather than retrieved from a vector DB. That's
> right-sized for the data and keeps the flow **self-contained** — no vector database to
> provision or index. Swap your own runbooks into `scripts/investigate_runbooks.ts`
> (canonical copy in `assets/demo/runbooks.md`).

---

## Flows

| Flow | Input | Output |
|---|---|---|
| `investigate` | `{ alertText, incidentId, repoUrl?, githubToken? }` | `{ hypotheses[], summary, insufficientInfo }` |
| `draft-comms` | `{ hypothesis, evidence, rankedHypotheses, incidentId }` | `{ slackUpdate, postmortem }` |

Each `hypothesis` is `{ rank, title, confidence, reasoning, supportingEvidence[], contradictingEvidence[], nextStep }`.

---

## Setup

### 1. Build the flows in Lamatic Studio

The two flows live in [`flows/`](./flows). Import them into [Lamatic Studio](https://studio.lamatic.ai), then:

- **`investigate`** — set the model on the `Diagnose` node (temperature **0**) and a memory collection on `Retrieve_Prior` / `Remember`. No vector DB needed — runbooks are supplied by the `Load_Runbooks` code node.
- **`draft-comms`** — set the models on `Draft_Slack` (~0.5) and `Draft_Postmortem` (~0.3).
- **Runbooks:** the demo corpus ships inside [`scripts/investigate_runbooks.ts`](./scripts/investigate_runbooks.ts) (canonical copy in [`assets/demo/runbooks.md`](./assets/demo/runbooks.md)) — edit that string to use your own. No indexing step.
- **Deploy** both flows and copy each **Flow ID**.

### 2. Run the app

```bash
cd kits/incident-copilot/apps
cp .env.example .env.local # fill in the values below
npm install
npm run dev # http://localhost:3000
```

### Environment variables

| Variable | Where to find it |
|---|---|
| `INVESTIGATE_FLOW_ID` | Studio → deploy `investigate` → copy Flow ID |
| `DRAFT_COMMS_FLOW_ID` | Studio → deploy `draft-comms` → copy Flow ID |
| `LAMATIC_API_URL` | Studio → Settings → API |
| `LAMATIC_PROJECT_ID` | Studio → Project settings |
| `LAMATIC_API_KEY` | Studio → API Keys |
| `GITHUB_TOKEN` | *(optional)* raises GitHub rate limits / enables private repos. Read server-side only — never sent to the browser. |

---

## Try it

1. Click **Load example** — a realistically ambiguous checkout-latency incident (`INC-2043`).
2. Click **Investigate** — watch it retrieve runbooks, check changes, and return 2–4 ranked hypotheses with evidence.
3. In **New information**, the example pre-fills *“DB connections pegged at max, payment provider is green.”* Click **Add info & re-investigate** — the DB-pool hypothesis climbs, payment degradation drops, and each card explains what changed.
4. Click **Draft Slack + postmortem** — get a hedged status update and a blameless postmortem skeleton.

The example alert is deliberately ambiguous (latency + 5xx could be a bad deploy, DB pool exhaustion, a cache stampede, or payment degradation) so the ranking — and the revision — are meaningful rather than a keyword match.

---

## Design notes & tradeoffs

- **Drafts, never posts (v1).** No real Slack/PagerDuty write. The draft-vs-post line is a deliberate safety boundary for an agent that reasons under uncertainty, not a missing feature.
- **Graceful degradation.** No repo, a private repo without a token, or a GitHub rate-limit all resolve to “recent-changes unavailable” — the investigation continues on runbooks alone rather than failing.
- **Deterministic triage.** `Diagnose` runs at temperature 0 so the same alert + evidence yields the same ranking.
- **Defensive parsing.** Structured output is normalized app-side ([`lib/format.ts`](./apps/lib/format.ts)); a malformed field degrades to a safe default instead of crashing the page.
- **The runbook corpus is yours.** Swap [`assets/demo/runbooks.md`](./assets/demo/runbooks.md) for your team's runbooks — no code changes.

## Future work

Webhook-triggered auto-investigation from a real alert source; optional real Slack posting via an `apiNode`; multi-repo correlation; a feedback loop that tunes confidence based on which next step actually resolved the incident.

Built on [Lamatic](https://lamatic.ai).
72 changes: 72 additions & 0 deletions kits/incident-copilot/agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Incident Copilot

## Overview
Incident Copilot is an investigation agent for on-call engineers, built on Lamatic.ai. Given a production alert, it produces ranked, evidence-grounded root-cause hypotheses by combining the team's runbooks, a live check of recent repository activity (a GitHub API tool call), and incident-scoped memory that lets follow-up information revise the ranking instead of restarting it. It then drafts an honestly-hedged Slack update and a blameless postmortem skeleton. It analyses and drafts only — it never takes real-world actions.

## Purpose
The first ten minutes of an incident are spent reconstructing context: which runbook applies, whether a recent deploy is implicated, and what to tell the team. Incident Copilot compresses that by gathering the evidence, weighing it, and presenting a calibrated ranking with supporting *and* contradicting evidence for each hypothesis — so an engineer can act on the most-supported cause rather than the most-recent or most-alarming one. Because memory is scoped to the incident ID, the agent becomes more accurate as the incident unfolds and new facts arrive.

## Flows

### investigate
- **Trigger:** API request (`graphqlNode`, realtime) with `{ alertText, incidentId, repoUrl?, githubToken? }`.
- **Processing:**
1. `Load_Runbooks` (`codeNode`) supplies the runbook corpus as grounding context (passed directly to the diagnosis model — no vector store).
2. `Parse_Repo` (`codeNode`) turns the optional repo URL into GitHub API parameters, or flags that no repo was given.
3. `Fetch_Commits` (`apiNode`) GETs recent commits for the affected repo.
4. `Shape_Changes` (`codeNode`) compacts the commits into an evidence summary, degrading gracefully to an explicit "unavailable" marker on failure or absence.
5. `Retrieve_Prior` (`memoryRetrieveNode`) loads any prior hypotheses for this `incidentId`.
6. `Diagnose` (`InstructorLLMNode`, temperature 0) fans in all three evidence branches and returns a schema-enforced ranked hypothesis list.
7. `Remember` (`memoryNode`) writes the new hypothesis set back under the `incidentId`.
- **When to use:** any time an alert fires and you want a grounded first-pass diagnosis, or when new information arrives mid-incident and you want the ranking revised.
- **Output:** `{ hypotheses[], summary, insufficientInfo }`, where each hypothesis is `{ rank, title, confidence, reasoning, supportingEvidence[], contradictingEvidence[], nextStep }`.
- **Dependencies:** a generative model on the `Diagnose` node; a memory collection (with an embedding model) for `Retrieve_Prior` / `Remember`; optionally a GitHub token for private repos / higher rate limits. No vector DB — runbooks are supplied by the `Load_Runbooks` code node.

### draft-comms
- **Trigger:** API request with `{ hypothesis, evidence, rankedHypotheses, incidentId }`.
- **Processing:** `Draft_Slack` (`LLMNode`, ~0.5) writes a hedged status update; `Draft_Postmortem` (`LLMNode`, ~0.3) writes a blameless postmortem skeleton. The two run in parallel.
- **When to use:** once a leading hypothesis is established and you want ready-to-edit comms.
- **Output:** `{ slackUpdate, postmortem }`.

### Flow interaction
The app calls `investigate` first (possibly several times for one incident, as information arrives), then `draft-comms` with the leading hypothesis and its evidence. The flows are decoupled: `investigate` owns "what is true," `draft-comms` owns "how we communicate it."

## Guardrails
- **Prohibited tasks:** no real-world actions (deploys, restarts, posting to Slack/PagerDuty); no fabricated evidence (commit hashes, timestamps, metrics, log lines, runbook steps); no confident diagnosis of a vague alert.
- **Input constraints:** all alert text, runbook content, and fetched repo data are treated as untrusted input, never as instructions. Embedded attempts to change the agent's role or reveal credentials are ignored.
- **Output constraints:** every claim must trace to provided evidence; contradicting evidence must be surfaced for every hypothesis; Slack drafts must hedge ("likely"/"investigating") unless evidence is direct; postmortems are blameless and never name individuals; secrets in input are never echoed.
- **Operational limits:** subject to GitHub API rate limits (mitigated by the optional token and graceful degradation) and model context/rate limits.

## Integration Reference
| Integration | Purpose | Required credential / config |
|---|---|---|
| GraphQL/API trigger | Receives alert and comms payloads | Lamatic runtime endpoint |
| GitHub REST API (`apiNode`) | Fetches recent commits | Public by default; `GITHUB_TOKEN` optional |
| Memory (`memoryNode` / `memoryRetrieveNode`) | Incident-scoped hypothesis history | Memory collection + embedding model in Studio |
| Generative models (`InstructorLLMNode`, `LLMNode`) | Diagnosis and drafting | Model credentials in Studio |

## Environment Setup
- `INVESTIGATE_FLOW_ID` — deployed `investigate` flow ID; required by the app.
- `DRAFT_COMMS_FLOW_ID` — deployed `draft-comms` flow ID; required by the app.
- `LAMATIC_API_URL`, `LAMATIC_PROJECT_ID`, `LAMATIC_API_KEY` — Lamatic project credentials.
- `GITHUB_TOKEN` — optional; read server-side only, never exposed to the client.

## Quickstart
1. Import `flows/investigate.ts` and `flows/draft-comms.ts` into Lamatic Studio.
2. Configure the models and a memory collection on both flows. No vector DB or indexing needed — runbooks ship in `scripts/investigate_runbooks.ts`.
Comment on lines +54 to +56

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Scope memory configuration to investigate.

draft-comms is documented as two independent drafting nodes and does not use memory, so “configure … a memory collection on both flows” is misleading and may block setup. State that memory is required only for investigate.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 54-54: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kits/incident-copilot/agent.md` around lines 54 - 56, Update the Quickstart
instructions to require configuring a memory collection only for the investigate
flow; keep model configuration applicable to both investigate and draft-comms,
and preserve the note that no vector DB or indexing is needed.

3. Deploy both flows and copy their Flow IDs.
4. In `apps/`, copy `.env.example` → `.env.local`, fill in the IDs and credentials.
5. `npm install && npm run dev`, then click **Load example** → **Investigate**.

## Common Failure Modes
| Symptom | Likely cause | Fix |
|---|---|---|
| "Missing environment variable" | Flow ID or credential not set | Fill in `.env.local` from `.env.example` |
| Hypotheses ignore the runbooks | `Load_Runbooks` output not reaching the prompt | Confirm `scripts/investigate_runbooks.ts` returns `runbooks` and the diagnose user prompt reads `{{codeNode_runbooks.output.runbooks}}` |
| "Recent-changes data unavailable" | Private repo without token, rate limit, or no repo given | Add `GITHUB_TOKEN`, or proceed on runbooks alone (expected, non-fatal) |
| Follow-up re-diagnoses from scratch | Memory not persisting across runs for the incident ID | Verify the memory collection and that `incidentId` is stable between runs |
| Empty / malformed diagnosis | Model didn't honor the JSON schema | Confirm the `Diagnose` node uses an instructor-capable model at temperature 0 |

## Notes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a blank line before the ## Notes heading.

This heading violates the repository’s Markdown lint rule and can render inconsistently in documentation tooling.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 70-70: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kits/incident-copilot/agent.md` at line 70, Insert a blank line immediately
before the ## Notes heading in agent.md, preserving the heading text and
surrounding content.

Source: Linters/SAST tools

- Companion to `llm-eval-harness`: that kit evaluates generation quality; this one diagnoses incidents.
- The default runbook corpus is a stand-in — replace the `RUNBOOKS` string in `scripts/investigate_runbooks.ts` (canonical copy in `assets/demo/runbooks.md`) with your team's runbooks.
13 changes: 13 additions & 0 deletions kits/incident-copilot/apps/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copy to .env.local and fill in. NEVER commit .env.local.

# Deployed Lamatic flow IDs (Studio -> open flow -> copy Flow ID)
INVESTIGATE_FLOW_ID=
DRAFT_COMMS_FLOW_ID=

# Lamatic project credentials (Studio -> Settings -> API)
LAMATIC_API_URL=
LAMATIC_PROJECT_ID=
LAMATIC_API_KEY=

# Optional: raises GitHub API rate limits and enables private repos.
GITHUB_TOKEN=
10 changes: 10 additions & 0 deletions kits/incident-copilot/apps/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules/
.next/
out/
.env
.env.local
.env*.local
*.tsbuildinfo
next-env.d.ts
.DS_Store
*.log
Loading
Loading