# Investibook Backend (Agent Runtime)

Python FastAPI backend using **ACP (Agent Client Protocol)** or the Pi core JSONL bridge to communicate with AI coding agents.

## Setup

```bash
cd backend
uv sync
```

## Configuration

### Investibook Settings

Configuration is loaded from YAML files and environment variables. Priority: **Environment variables > YAML file > defaults**

#### Config File Locations (checked in order)

1. Path specified in `INVESTIBOOK_CONFIG_FILE` environment variable
2. `../config.yaml` (parent directory, useful when running from `backend/`)
3. `../investibook.yaml` (parent directory)
4. `./investibook.yaml` (current directory)
5. `./config/investibook.yaml`
6. `~/.investibook/config.yaml`

Copy `investibook.yaml.example` to `investibook.yaml` and customize the values.

#### Available Settings

| Setting | Environment Variable | Default | Description |
|---------|---------------------|---------|-------------|
| `aiops_api_url` | `INVESTIBOOK_AIOPS_API_URL` | `""` | Guardian API base URL |
| `s3_bucket` | `INVESTIBOOK_S3_BUCKET` | `tidbcloud-aiops-assets-dev` | S3 bucket for analysis files |
| `aws_region` | `INVESTIBOOK_AWS_REGION` | `us-west-2` | AWS region for S3 |
| `clinic_endpoint` | `INVESTIBOOK_CLINIC_ENDPOINT` | `https://clinic.pingcap.com` | Clinic HTTP API endpoint |
| `clinic_api_key` | `INVESTIBOOK_CLINIC_API_KEY` | `""` | Clinic API key |
| `clinic_cookie` | `INVESTIBOOK_CLINIC_COOKIE` | `""` | Clinic cookie (for local debugging) |
| `clinic_csrf_token` | `INVESTIBOOK_CLINIC_CSRF_TOKEN` | `""` | Clinic CSRF token (for local debugging) |
| `jira_base_url` | `INVESTIBOOK_JIRA_BASE_URL` | `https://pingcap.atlassian.net` | Jira base URL |
| `jira_username` | `INVESTIBOOK_JIRA_USERNAME` | `""` | Jira username (email) |
| `jira_api_token` | `INVESTIBOOK_JIRA_API_TOKEN` | `""` | Jira API token |
| `openai_api_key` | `INVESTIBOOK_OPENAI_API_KEY` | `""` | OpenAI API key for comment summarization (gpt-4o) |
| `openai_model` | `INVESTIBOOK_OPENAI_MODEL` | `gpt-4o` | OpenAI model for comment summarization |
| `callback_api_key` | `INVESTIBOOK_CALLBACK_API_KEY` | `""` | API key sent in AutoChat callback header `X-API-Key` |

**Note:** AWS credentials (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_PROFILE`) are read by boto3 natively.

### Agent Runtime Settings

| Variable | Default | Description |
|----------|---------|-------------|
| `ACP_AGENT_TYPE` | `opencode` | Agent to use: `opencode`, `claude`, or `pi` |
| `ACP_AGENT_BIN` | auto-detect | Optional absolute path to the selected runtime executable. If unset, backend uses `PATH` to find `opencode`, `claude-code-acp`, or the Bun-compiled `pi-core` host. |
| `AGENT_PROVIDER_CONFIG` | `$XDG_CONFIG_HOME/opencode/opencode.jsonc` | Shared provider/model catalog. Runtime adapters translate this source config into each agent's native format. |
| `AGENT_MODEL_STANDARD` / `AGENT_MODEL_PRO` | _(agent default)_ | Shared standard/pro selectors, for example `chat/claude-standard/high`. |
| `ACP_MODEL_STANDARD` / `ACP_MODEL_PRO` | _(unset)_ | Optional OpenCode/Claude-specific compatibility overrides. |
| `PI_MODEL_STANDARD` / `PI_MODEL_PRO` | _(unset)_ | Optional Pi-specific overrides when a provider cannot be adapted, for example `openai/gpt-5.2` or `anthropic/claude-sonnet-4-20250514:high`. |

The backend Docker image installs two Bun-compiled standalone executables: the Investibook-pinned `opencode` artifact and the thin Investibook `pi-core` host. It also bundles the OpenCode telemetry plugin into one JavaScript file. Node, the Pi `node_modules` tree, and the full Pi coding-agent CLI/TUI are not part of the final image. CI downloads OpenCode from the latest successful `tidbcloud/opencode` `build-branch-opencode.yml` artifact for `OPENCODE_ARTIFACT_REF` (defaults to `dev`) before building the image. Run `make build-agent-runtime` to build both local runtime artifacts.

Outside a sandbox, the backend prefers `pi-core` from `PATH` and falls back to running `agentkit/runtime/pi-core/index.mjs` with Bun or Node for local development. Run9 prefers the standalone binary baked into its snap (`RUN9_PI_CORE_BIN`) and retains the Node source fallback so older snaps remain usable during rollout. Run9 workspaces link the bundled telemetry plugin from the snap and never share the backend pod's `/data/opencode-config` directory.

Pi runs through a JSONL host built directly on `pi-agent-core`, with workspace-local sessions under `.pi/sessions`. Investibook adapts the shared provider catalog into `.pi/core/runtime-config.json`, reuses ambient provider credentials such as `AWS_BEARER_TOKEN_BEDROCK`, and mirrors enabled skills into `.pi/skills/`. Runtime-specific model variables are only needed as escape hatches for providers without an adapter.

## Running

```bash
# Using OpenCode (default)
uv run uvicorn main:app --host 0.0.0.0 --port 8000

# Using OpenCode from an explicit local path
ACP_AGENT_BIN=/path/to/opencode uv run uvicorn main:app --host 0.0.0.0 --port 8000

# Using Claude
ACP_AGENT_TYPE=claude uv run uvicorn main:app --host 0.0.0.0 --port 8000

# Using Pi core
ACP_AGENT_TYPE=pi uv run uvicorn main:app --host 0.0.0.0 --port 8000

```

## API Endpoints

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/chat` | Non-streaming chat |
| POST | `/api/sessions` | Create or fetch a workspace agent session |
| POST | `/api/sessions/{id}/turns` | Execute an idempotent durable turn |
| POST | `/api/sessions/{id}/turns/stop` | Stop the active turn |
| GET | `/api/sessions/{id}` | Fetch the session and recent turn history |
| GET | `/api/sessions/{id}/transcript` | Fetch durable transcript records |
| GET | `/api/sessions/{id}/transcript/events` | Stream transcript records through SSE |
| GET | `/api/chat/stream` | Compatibility SSE adapter over agent turns |
| GET | `/api/session/new` | Create new session |
| GET | `/api/session/{id}/history` | Get message history |
| DELETE | `/api/session/{id}` | Delete session |
| GET | `/health` | Health check |

## How it works

1. On first chat request, the backend spawns the selected agent as a subprocess
2. OpenCode/Claude communicate over ACP; the Pi core host communicates over a small JSONL bridge
3. `AgentEventBridge` normalizes ACP callbacks and Pi core events into one event model
4. A durable worker owns each prompt and persists message snapshots independent of clients
5. Web and CLI clients observe normalized events through SSE; disconnecting only detaches
