# autopilot

`autopilot/` contains a diagnosis agent for TiDB Cloud NextGen workflows in `auto-deploy`. The agent is driven by a GitHub issue number, reads the issue thread, fetches the failed workflow run logs, identifies the root cause, and posts a diagnosis report back to the issue.

The implementation is built on [pydantic-ai](https://ai.pydantic.dev/) and composes a small set of general-purpose capability modules (GitHub issues, GitHub Actions workflows, filesystem access, skills). All diagnosis logic lives in skill guides under `skills/`.

> **Status**: prototype — implements a concrete diagnosis flow driven by `auto-diagnose.yml`.

## Problem It Solves

Workflow failures in auto-deploy are operationally noisy:

- logs are large and hard to triage quickly
- failures span multiple repositories (auto-deploy, app-delivery)
- operators need to correlate log evidence with source code to identify root causes

This agent centralizes that flow behind the GitHub issue thread so the issue becomes both the diagnosis request and the audit trail.

## Scope

The agent diagnoses workflow failures across **any environment** (dev, staging, prod) and **any repository** (`auto-deploy`, `app-delivery`, or others). It is read-only — it never triggers workflows, opens PRs, or modifies files.

When the root cause is a code bug, the agent includes a **Suggested fix** section in its report (file path, relevant lines, recommended change) for the operator to apply manually.

## How The Agent Works

At a high level:

1. Read the issue body and extract `failed_run_id` and `failed_repo`.
2. Check for a previous `<!-- autopilot:done -->` marker — if found, stop to avoid duplicating work.
3. Load the `debug-workflow` skill and follow its triage procedure.
4. Fetch run logs via `get_workflow_run_logs`, read workflow definitions from local checkouts.
5. Classify the root cause: transient, config error, app-delivery failure, code bug, or unknown.
6. Post the diagnosis report to the issue via `publish_issue_update`.

The issue thread is the state surface. A `<!-- autopilot:done -->` marker embedded in the final comment prevents re-running on already-diagnosed issues.

## Safety Model

The agent is strictly read-only:

- never triggers a new deploy, destroy, or rerun of any workflow
- never opens a pull request or modifies any file in any repository
- never closes or reopens issues or PRs

## Structure

```
autopilot/
├── main.py              # Entry point: issue number → diagnose_agent; no args → chat web UI
├── agent.py             # diagnose_agent + chat_agent definitions, make_agent() factory
├── agent_output.py      # AgentIssueUpdate output type
├── config.py            # Model, repo, and other constants
├── instructions/
│   ├── diagnose.tmpl        # System prompt for diagnose_agent (string.Template)
│   └── chat.tmpl            # System prompt for chat_agent (string.Template)
├── capabilities/
│   ├── base.py              # CapabilityWithTools base class
│   ├── fs.py                # Filesystem access (read/write/edit/ls/glob/grep)
│   ├── git.py               # Git and GitHub PR read tools (list_prs, get_pr_info, get_pr_diff)
│   ├── github_issues.py     # GitHubIssuesCapability (3 tools) + DiagnoseIssuesCapability (+ after_output_process)
│   ├── github_workflows.py  # Monitor GitHub Actions runs and fetch logs
│   └── sleep.py             # Sleep tool for polling loops
├── skills/
│   ├── debug-workflow/      # Workflow failure triage guide
│   ├── argocd-apps/         # Read ArgoCD app status from GKE
│   └── create-skill/        # Authoring guide for new skills
└── Makefile                 # fmt / check / typecheck
```

## Capabilities

Each capability extends `AbstractCapability`, registers tools via `get_toolset()`, and injects background instructions into the agent.

| Capability | Used by | Tools | Description |
|---|---|---|---|
| `GitHubIssuesCapability` | chat agent | `get_issue_body`<br>`list_issue_comments`<br>`comment_on_issue` | Read the issue thread and post comments |
| `DiagnoseIssuesCapability` | diagnose agent | above + `after_output_process` hook | Extends base; publishes the final `AgentIssueUpdate` as a GitHub comment |
| `GitHubWorkflowsCapability` | both agents | `get_workflow_run_status`<br>`get_workflow_run_logs`<br>`list_workflow_runs`<br>`wait_for_workflow_run` | Monitors GitHub Actions runs and downloads logs to `/tmp/gh_run_<id>.log` |
| `FSCapability` | both agents | `read_file` `write_file` `edit_file`<br>`ls` `glob` `grep` | Filesystem access |
| `SleepCapability` | chat agent | `sleep_seconds` | Pause execution for polling loops |
| `SkillsCapability` | both agents | `list_skills`<br>`load_skill` | Discover and load skill guides |

## Skills

| Skill | Description |
|---|---|
| `debug-workflow` | Triage guide for GitHub Actions failures — fetch logs, read workflow definitions, classify root cause, report findings |
| `argocd-apps` | Read ArgoCD app status from GKE via kubectl (no ArgoCD CLI or SSO needed) |
| `create-skill` | Authoring guide for creating new skills |

## Running

- Python `>=3.14`
- Dependencies are declared inline in the script header via [PEP 723](https://peps.python.org/pep-0723/)
- Use [uv](https://docs.astral.sh/uv/)
- Set `LOGFIRE_TOKEN` if you want traces sent to Logfire

```bash
# Run the diagnosis agent on a GitHub issue
uv run main.py <issue_number>

# Start the chat agent web UI at http://127.0.0.1:7932
uv run main.py
```

## Development

```bash
make fmt
make check
make typecheck
```

## Design Notes

- **GitHub issue as interface**: the diagnosis request, log evidence, and operator-visible report all live in one thread.
- **Thin agent, thick skills**: the agent's capabilities are general-purpose; all triage logic lives in skill guides that are easy to read and update independently.
- **Read-only by construction**: the agent has no tools that write to repositories or trigger workflows — safety is enforced at the capability layer, not just in prompt text.
- **Idempotent re-runs**: the `<!-- autopilot:done -->` marker prevents duplicate diagnosis comments if the agent is re-invoked on an already-diagnosed issue.
