---
title: "Investigations"
description: "AI-assisted root-cause analysis: generate hypotheses and prompt fixes for recurring call issues, test them, and apply the winner."
---

An **investigation** takes a cluster of related
[issue reports](/api-reference/issue-reports) on one agent and works
toward a prompt fix: the AI generates **hypotheses** about the root
cause, drafts **prompt candidates** (you can add your own), each
candidate can be tested in an **experiment** (replaying the failing
calls' inputs against the candidate prompt), and the winning candidate
is **applied** to the agent.

Status flow: `idle` → `generating_hypotheses` → `awaiting_candidates`
→ `searching` → `ready` → `applied`. A run can instead end as
`not_replayable` or `not_reproducible`, and you can set it to `dismissed`.
Starting an investigation kicks off the whole flow automatically —
diagnosis, candidate prompt changes, and replay testing for each one.
Steps run asynchronously — poll the detail endpoint.

## Endpoints

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/v1/investigations` | List investigations (`?agent_id=`, `?status=`) |
| `POST` | `/v1/investigations` | Start an investigation |
| `GET` | `/v1/investigations/{investigation_id}` | Full detail with candidates + experiments |
| `PATCH` | `/v1/investigations/{investigation_id}` | Update `experiment_config`, or dismiss |
| `POST` | `/v1/investigations/{investigation_id}/hypotheses` | Re-kick diagnosis on an idle investigation |
| `POST` | `/v1/investigations/{investigation_id}/candidates/manual` | Add your own candidate prompt |
| `POST` | `/v1/investigations/{investigation_id}/candidates/{candidate_id}/experiments` | Run an experiment for a candidate |
| `GET` | `/v1/investigations/{investigation_id}/experiments/{experiment_id}` | Poll one experiment |
| `POST` | `/v1/investigations/{investigation_id}/apply` | Apply a candidate's prompt to the agent |

All ids on this page are **UUIDs** except `agent_id` and `call_ids`.

## Investigation object

```json
{
  "id": "e7a91c2d-…",
  "issue_pattern_id": "long-pauses-before-answering",
  "issue_trace_ids": ["5f9a…", "77b1…"],
  "agent_id": 12,
  "baseline_prompt": "You are a helpful support agent…",
  "call_ids": [987654321, 987654322],
  "status": "ready",
  "experiment_config": { "sample_size": 5 },
  "hypotheses": [
    { "id": "h1", "title": "Prompt over-constrains responses", "rationale": "…" }
  ],
  "baseline_results": null,
  "candidates": [
    {
      "id": "c3d4…",
      "source": "ai",
      "hypothesis_id": "h1",
      "label": "Loosen response constraints",
      "prompt": "You are a helpful support agent… (revised)",
      "rationale": "…",
      "created_at": "2026-04-20T18:30:00.000Z",
      "latest_experiment_id": "a1b2…"
    }
  ],
  "experiments": [
    {
      "id": "a1b2…",
      "candidate_id": "c3d4…",
      "status": "completed",
      "config": { "sample_size": 5 },
      "samples": [
        {
          "id": "s1…",
          "call_id": 987654321,
          "sample_index": 0,
          "user_input": "…",
          "original_output": "…",
          "replayed_output": "…",
          "grade": { "verdict": "improved" },
          "created_at": "2026-04-20T18:31:00.000Z"
        }
      ],
      "summary": { "improved": 4, "regressed": 0, "unchanged": 1 },
      "created_at": "2026-04-20T18:30:30.000Z",
      "completed_at": "2026-04-20T18:32:10.000Z",
      "error_message": null
    }
  ],
  "applied_candidate_id": null,
  "applied_agent_version": null,
  "error_message": null,
  "created_at": "2026-04-20T18:24:10.113Z",
  "updated_at": "2026-04-20T18:32:10.000Z"
}
```

The **list** endpoint returns `{"results": [...]}` with the same
fields minus the nested `candidates` and `experiments`.

| Field | Type | Description |
|-------|------|-------------|
| `id` | UUID | Investigation id |
| `issue_pattern_id` | string | Your identifier for the issue cluster (≤ 255 chars) |
| `issue_trace_ids` | array of string | The issue reports under investigation |
| `agent_id` | integer | The agent being fixed |
| `baseline_prompt` | string | Snapshot of the agent prompt when the investigation started |
| `call_ids` | array of integer | Example failing calls used as evidence |
| `status` | string | `idle`, `generating_hypotheses`, `awaiting_candidates`, `generating_candidates` (historical), `searching`, `ready`, `not_replayable`, `not_reproducible`, `applied`, or `dismissed` |
| `experiment_config` | object | Default config passed to experiments |
| `hypotheses` | array | AI-generated root-cause hypotheses |
| `candidates` | array | Prompt candidates — `source` is `ai` or `manual`; `hypothesis_id` links back to a hypothesis |
| `experiments` | array | All experiments across candidates, newest first. Sample `grade` compares `original_output` vs `replayed_output` |
| `applied_candidate_id` | UUID \| null | Set once applied |
| `applied_agent_version` | integer \| null | The [agent version](/api-reference/agents#version-history) revision the apply created |

---

## Start an investigation

<CodeGroup>
```bash cURL
curl -X POST https://api.thunderphone.com/v1/investigations \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "issue_pattern_id": "long-pauses-before-answering",
    "issue_trace_ids": ["5f9a…", "77b1…"],
    "agent_id": 12,
    "call_ids": [987654321, 987654322]
  }'
```
</CodeGroup>

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `issue_pattern_id` | string | yes | ≤ 255 chars |
| `issue_trace_ids` | array of string | yes | Non-empty |
| `agent_id` | integer | yes | Must belong to your org (`404` otherwise) |
| `call_ids` | array of integer | yes | Non-empty |
| `experiment_config` | object | no | Defaults to `{}` |

Returns `201` with the investigation already running
(`status="generating_hypotheses"`) — creation launches the full flow
automatically: diagnosis and baseline replays, then the autonomous fix
search. Poll `GET …/{investigation_id}` until `status="ready"` and read
`search_result`.

## Drive the workflow

1. **Poll `GET …/{investigation_id}`** — the investigation advances
   through `generating_hypotheses` → `awaiting_candidates` →
   `searching` → `ready` on its own.
2. **`POST …/candidates/manual`** — add your own candidate at any
   non-terminal point: `{"label", "prompt", "rationale"?}` → `201`
   with the new candidate. Manual candidates join the next search
   round, or can be tested directly (next step).
3. **`POST …/candidates/{candidate_id}/experiments`** — body
   `{"config": {…}}` (optional). Replays the failing calls' inputs
   against the candidate prompt asynchronously. Returns `201` with the
   experiment (`status: "running"`); poll
   `GET …/experiments/{experiment_id}` until `completed` / `failed`.
4. **`POST …/apply`** — body `{"candidate_id": "…"}`. Writes the
   candidate's prompt to the agent, records an agent config version,
   sets status `applied`, and auto-resolves the investigation's
   linked issue reports. Allowed from `ready` / `awaiting_candidates`
   (`409` otherwise). Returns the final investigation.
5. **`POST …/hypotheses`** — re-kick diagnosis on an investigation
   that ended `idle` (for example after an interrupted run). `409`
   when a run is already in progress.

To abandon an investigation:
`PATCH /v1/investigations/{id}` with `{"status": "dismissed"}` —
`dismissed` is the only status a client may set directly.

<Note>
  `apply` merges the fix into the agent's **draft** when the exact prompt
  section it was based on still matches. Unrelated draft edits are preserved,
  and the live prompt is untouched until you deploy. Pass `"deploy": true` to deploy in the same call, and
  `"with_ack": true` on a tier-upgrade candidate to enable
  acknowledgements at the same time. A `409` means the relevant prompt section
  changed or its anchor is ambiguous. Re-run the investigation against the
  current prompt.
</Note>

## Run the fix search

`POST /v1/investigations/{investigation_id}/search`

Runs the autonomous fix search: the AI drafts diverse prompt changes (and,
for eligible agents, a tier upgrade), screens each one in parallel replays of
the AI turn, culls weak variants, and confirms the best result on fresh
replays plus calls without the issue. The investigation's `search_result`
carries the outcome — `tier` is `fixed` (eliminated in a confirmation round),
`improved` (a confirmed reduction), or `null` when no change was confirmed.
A completed search with no winner flags the cluster to ThunderPhone with the
replay evidence; when `stopped_early` is `"testing_incomplete"` or
`"replay_budget_exhausted"` the search could not finish verifying its
candidates, nothing is flagged, and rerunning the search is the right next
step (previous searches' best unconfirmed changes are carried into the new
round automatically).

---

## Related

<CardGroup cols={2}>
  <Card title="Fix issues in the dashboard" icon="wand-magic-sparkles" href="/guides/issues#fix-with-ai">
    See the same investigation flow from the Issues page.
  </Card>
  <Card title="Issue Reports" icon="triangle-exclamation" href="/api-reference/issue-reports">
    The raw material investigations start from.
  </Card>
  <Card title="Test Scenarios" icon="list-check" href="/api-reference/test-scenarios">
    Guard the fix with a promoted regression scenario.
  </Card>
  <Card title="Agents" icon="robot" href="/api-reference/agents">
    Applied fixes appear in the agent's version history.
  </Card>
  <Card title="Calls" icon="phone" href="/api-reference/calls">
    Evidence calls are ordinary call logs.
  </Card>
</CardGroup>
