ef67cd2d15
Piloto 0120 convergio en 2 iter (2m28s). PR creado en dataforge/chart_demo/pulls/1 (no en dataforge/fn_registry — sub-repo). Anadido a autonomous_loop.md: - Seccion "Sub-repos vs worktree padre": orquestador opera en sub-repo cuando issue toca apps/, projects/*/apps/, cpp/apps/ o analysis/. - Seccion "Gitea API vs gh": gh auth es smoke, real es curl + pass token. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
103 lines
7.1 KiB
Markdown
103 lines
7.1 KiB
Markdown
## Bucle autonomo (`fn-orquestador` + `/autonomous-task`) — issue 0069
|
|
|
|
`fn-orquestador` recorre el ciclo reactivo (CONSTRUIR → EJECUTAR → RECOPILAR → ANALIZAR → MEJORAR) sin intervencion humana, hasta convergencia (suite verde), estancamiento (no progreso N iteraciones), timeout, o tope de iteraciones. Trabaja SIEMPRE en sandbox `auto/<issue>`, NUNCA merge a master.
|
|
|
|
### Cuando se invoca
|
|
|
|
- Skill `/autonomous-task <issue_id>` (humano lanza explicitamente).
|
|
- Cron / dag_engine (`schedule:` en YAML; planificable, no implementado por defecto).
|
|
- NO se invoca como reaccion a hooks ni a fallos de tests "en caliente". Siempre tarea explicita.
|
|
|
|
### Reglas duras
|
|
|
|
1. **Sandbox obligatorio**: rama `auto/<issue_id>-<slug>`. Si la rama existe -> reset hard contra master y reanudar. NUNCA commits a master, NUNCA push --force-with-lease a master.
|
|
2. **Paths protegidos**: respetar `dev/autonomous_protected_paths.json` exactamente. Cualquier intento de modificar un path protegido aborta la iteracion y registra `task_runs.status='aborted_protected_path'`.
|
|
3. **Filtro de proposals auto-aplicables**: el orquestador SOLO aplica proposals que cumplen:
|
|
- `kind in (bug_fix, e2e_check_add, doc_update, capability_tag_add)` -> auto-aplicable.
|
|
- `kind in (new_function, deprecate_function, refactor, schema_change)` -> NO auto-aplicable (queda `pending` para humano).
|
|
- `priority in (low, medium)` -> auto-aplicable. `high|critical` -> requiere humano salvo override `--allow-high`.
|
|
4. **Watchdog**: si la metrica de progreso (`checks_pass / checks_total`) no sube en `N=3` iteraciones consecutivas -> abort. Registrar `task_runs.status='stalled'`.
|
|
5. **Tiempo**: cada `task_run` con timeout default 30 min. Override con `--timeout-min N` hasta max 4h.
|
|
6. **Idempotencia**: re-ejecutar `/autonomous-task <id>` sobre la misma issue reanuda desde la ultima iteracion exitosa, NO reinicia desde cero (lookup en `task_runs` por `issue_id`).
|
|
7. **Trazabilidad**: cada decision se persiste en `task_runs.events_json[]` con `{ts, agent, action, evidence, diff_summary}`. El humano puede leer el log entero para auditar.
|
|
8. **No self-modification**: orquestador NUNCA modifica `.claude/agents/`, `.claude/commands/`, `.claude/rules/`, `.claude/scripts/`, `.claude/CLAUDE.md`. Reforzado en `autonomous_protected_paths.json`.
|
|
9. **NUNCA paths absolutos fuera del worktree**. Refuerzo del piloto 1 (2026-05-15): el orquestador uso `/home/lucas/fn_registry/bash/functions/...` para fixear hooks bash y contamino el repo principal. Solucion correcta: fix vive solo en el worktree. Post-cada-iteracion: `git -C <main_repo> status --short` debe permanecer igual al baseline; cualquier diff = `status=sandbox_breach` -> ABORT.
|
|
10. **Pre-commit hooks compartidos**. Worktrees comparten `.git/hooks/` con main. Si un hook llama scripts via path absoluto, ejecutara la version de main. Si el hook bloquea progreso por bug en main: aplica el fix EN EL WORKTREE (commit en auto/*); si el bug del hook excede scope: `git commit --no-verify` para ESE commit con `task_runs.events_json[].decision="skip_hook"` + razon. NO editar main.
|
|
|
|
### Sub-repos vs worktree padre
|
|
|
|
Cuando el issue toca `app.md` o codigo dentro de `apps/<name>/`, `projects/<p>/apps/<name>/`, `cpp/apps/<name>/`, o `analysis/<a>/` — estos directorios son **sub-repos Gitea independientes** y estan `.gitignore`d en el repo padre `fn_registry` (regla `apps_subrepo.md`). El orquestador:
|
|
|
|
- **Crea worktree padre** `auto/<issue>` en `/tmp/fn_orq_<issue>_<ts>/` por protocolo, **pero no escribe alli** porque los cambios no se versionan en el padre.
|
|
- **Opera DIRECTAMENTE en el sub-repo** de la app/analysis target. Branch `auto/<issue>-<slug>` se crea dentro de `apps/<name>/.git`, NO en el padre.
|
|
- **PR draft sale al sub-repo** en `dataforge/<name>` (NO a `dataforge/fn_registry`). Humano revisa+mergea en el sub-repo.
|
|
- **Worktree padre queda vacio** y se limpia normal con `git worktree remove` al terminar.
|
|
|
|
Validado en piloto 0120 (`add_e2e_check` sobre `chart_demo`): PR creado en `dataforge/chart_demo/pulls/1`, sanity check del main repo `fn_registry` confirmo cero contaminacion.
|
|
|
|
Si el issue toca AMBOS lados (codigo del registry padre + app de sub-repo), el orquestador commitea separado: cambios del padre en `auto/<issue>` (worktree padre), cambios de la app en `auto/<issue>-<slug>` (sub-repo). Dos PRs draft. Humano coordina merge.
|
|
|
|
### Gitea API vs `gh`
|
|
|
|
Pre-condicion `gh auth status` es smoke check (target github.com). Mecanismo real de PR es `curl` a Gitea API:
|
|
|
|
```bash
|
|
GITEA_TOKEN=$(pass gitea/dataforge-git-token | head -n1)
|
|
curl -X POST -H "Authorization: token $GITEA_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"title":"...","head":"auto/<issue>-<slug>","base":"master","draft":true,"body":"..."}' \
|
|
"https://gitea-.../api/v1/repos/dataforge/<repo>/pulls"
|
|
```
|
|
|
|
Validado en pilotos 0076 y 0120.
|
|
|
|
### Estructura task_run
|
|
|
|
Migration `fn_operations/migrations/006_task_runs.sql`. Campos minimos: `id`, `issue_id`, `branch`, `started_at`, `finished_at`, `status` (`running|done|failed|aborted_protected_path|stalled|timeout`), `iterations`, `checks_pass`, `checks_fail`, `proposals_applied_json`, `proposals_skipped_json`, `events_json`, `final_diff_sha`.
|
|
|
|
### Fases por iteracion
|
|
|
|
```
|
|
loop:
|
|
1. fn-constructor (Read+Edit+Write+Bash limitados) - aplica fix segun ultima proposal seleccionada
|
|
2. fn-executor - corre build + tests + smoke
|
|
3. fn-recopilador - audita operations.db de la app
|
|
4. fn-analizador - corre e2e_checks (registra e2e_runs)
|
|
5. SI todos los checks pasan -> commit + push rama + abre PR. status=done. exit.
|
|
6. SI no progreso N iteraciones -> abort. status=stalled.
|
|
7. fn-mejorador - crea proposals desde fallos
|
|
8. orquestador filtra proposals auto-aplicables -> selecciona la primera -> goto 1.
|
|
```
|
|
|
|
### Output al humano
|
|
|
|
```
|
|
=== /autonomous-task 0068 ===
|
|
task_run_id: run_e2e_a1b2c3
|
|
branch: auto/0068-e2e-validation
|
|
iterations: 4
|
|
status: done
|
|
checks_pass: 8/8
|
|
proposals_applied: 3 (run_e2e_run_001, run_e2e_run_002, run_e2e_run_003)
|
|
proposals_skipped: 1 (refactor — needs human review)
|
|
PR: https://gitea.../pulls/42
|
|
```
|
|
|
|
### Anti-patrones
|
|
|
|
| Anti-patron | Por que es malo |
|
|
|---|---|
|
|
| Mergear `auto/<issue>` a master sin PR + humano | Salta gate, riesgo de regresion |
|
|
| Auto-aplicar proposal `kind=refactor` | Cambios sistemicos requieren revision |
|
|
| Modificar `go.sum`, `package-lock.json`, `uv.lock` | Cambios de deps requieren CVE/license review |
|
|
| Bucle infinito sin watchdog | Coste descontrolado de tokens |
|
|
| Borrar archivos sin backup en `task_runs.events_json` | Pierde auditoria |
|
|
| Override de paths protegidos via env var | Bypass de seguridad |
|
|
|
|
### Relacion con otras reglas
|
|
|
|
- [[e2e_validation]] — fn-analizador (fase 4) lee el contrato `e2e_checks` que el orquestador usa como gate.
|
|
- [[apps_tbd]] — el orquestador opera en rama `auto/*`, no exenta de TBD.
|
|
- [[feature_flags]] — si el fix no esta terminado, el orquestador puede meterlo detras de flag OFF antes de PR.
|
|
- [[registry_calls]] — toda invocacion del orquestador y sub-agentes pasa por MCP/`fn run`/heredoc canonico, registrada en call_monitor.
|