---
name: agent_runner_api
lang: go
domain: agents
version: 0.1.0
description: "Service Go que orquesta agentes Claude headless en git worktrees con DoD"
tags: [service, agents, go, workflows, dod]
icon:
  phosphor: "robot"
  accent: "#3b82f6"
framework: "stdlib-http"
entry_point: "main.go"
dir_path: "apps/agent_runner_api"
repo_url: "https://gitea.organic-machine.com/dataforge/agent_runner_api"
uses_functions: []
uses_types: []
service:
  port: 8486
  health_endpoint: /api/health
  health_timeout_s: 3
  systemd_unit: agent_runner_api.service
  systemd_scope: user
  restart_policy: always
  runtime: systemd-user
  pc_targets:
    - aurgi-pc
    - home-wsl
  is_local_only: true
e2e_checks:
  - id: build
    cmd: "CGO_ENABLED=1 go build -o agent_runner_api ."
    timeout_s: 120
  - id: smoke
    cmd: "./agent_runner_api --port 8486 --db /tmp/agent_runner_api_e2e.db &"
    health: "http://127.0.0.1:8486/api/health"
  - id: tests
    cmd: "go test -count=1 ./..."
---

## Visual

Backend puro, sin UI. Consume por skill_tree v2 + kanban_cpp.

## Endpoints

- `GET /api/health` — `{status, port, db}`.
- `POST /api/runs` — body `{issue_id?, card_id?, kanban_app?, mode, prompt?}` -> `{run_id, branch, worktree_path, sse_url}`. Crea worktree + lanza subprocess Claude (o `echo STUB:` si `AGENT_RUNNER_STUB=1`).
- `GET /api/runs?status=&app=&since=` — lista runs filtrada.
- `GET /api/runs/:id` — detalle + dod_items.
- `GET /api/runs/:id/sse` — stream `text/event-stream` (events: `connected`, `status`, `evidence`, `validated`, `merged`, `aborted`).
- `POST /api/runs/:id/evidence` — `{item_id|item_key, kind, payload_path?, payload_url?, payload_text?}`. Auto-crea `dod_item` si solo se da `item_key`.
- `POST /api/runs/:id/evidence/:eid/validate` — `{validated_by}`.
- `POST /api/runs/:id/merge` — TBD merge `auto/<branch>` a master `--no-ff` (gate: todos los `dod_items.required = 1` deben tener evidencia `validated`).
- `POST /api/runs/:id/abort` — kill PID + `git worktree remove --force` + `branch -D` + `status=aborted`.

## Schema (5 migrations idempotentes via embed.FS)

| Tabla | Para que |
|---|---|
| `workflows` | Templates de prompt + `dod_schema_json` |
| `runs` | Run vivo: workflow/issue/card/kanban_app + branch + worktree_path + PID + status |
| `worktrees` | 1 row por worktree creada, marcada `removed_at` al abort/merge |
| `dod_items` | Items DoD del run (`pending|done|validated|failed`) |
| `dod_evidence` | Evidencias adjuntas (`text|file|url`) con `validated_at/validated_by` |

## Lanzamiento

```bash
cd apps/agent_runner_api
CGO_ENABLED=1 go build -o agent_runner_api .
./agent_runner_api --port 8486 --db agent_runs.db --repo-root /home/lucas/fn_registry --worktrees-root /tmp
```

systemd-user: `systemctl --user enable --now agent_runner_api.service` (despues de copiar `agent_runner_api.service` a `~/.config/systemd/user/`).

## Gotchas

- `git worktree add` falla si la rama ya existe -> el `Spawn()` la borra antes con `branch -D` (best-effort).
- Worktree y main repo comparten `.git/hooks/` — pre-commit del main puede bloquear commits del agente; usar `--no-verify` documentado.
- `claude --headless` requiere PATH correcto en systemd. Si `claude` no esta en `$PATH`, el subprocess cae automaticamente a `echo STUB:` (mismo comportamiento que `AGENT_RUNNER_STUB=1`).
- Subprocess corre async — el handler HTTP devuelve `run_id` apenas inserta + lanza, no espera al exit.
- SSE: clientes deben reconectar al cerrar conexion. Heartbeat cada 15s para mantener conexion abierta.

## Capability growth log

- v0.1.0 (2026-05-18) — scaffold inicial: stdlib http, embed.FS migrations, SSE hub, spawn stub fallback, DoD gate en merge.
