Files
fn_registry/.claude/rules/autonomous_loop.md
T
egutierrez f36d091704 docs(0120): hallazgos piloto + regla sub-repos/Gitea API en autonomous_loop
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>
2026-05-19 00:19:30 +02:00

7.1 KiB

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 .gitignored 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:

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.