feat(infra): agent_launch_worktree + agent_cleanup_worktree Go fns

Two Go functions in functions/infra/ for orchestrating headless Claude
agents inside isolated git worktrees:

- AgentLaunchWorktree(cfg): creates worktree off master, spawns
  claude -p in background, redirects stdout/stderr to LogPath. Falls
  back to echo stub when claude binary missing (CI/test friendly).
  ResetIfExists support for re-runs.

- AgentCleanupWorktree(repo, branch, path, pid): SIGTERM with 1s
  grace then SIGKILL, git worktree remove --force, git branch -D.
  Best-effort: only errors when all three steps fail (idempotent
  cleanup-twice).

Promotes inline bash from .claude/skills/parallel-fix-issues/ and
fn-orquestador to first-class registry functions. Closes issue 0115.

Capability group: agents.
This commit is contained in:
2026-05-18 18:24:08 +02:00
parent 4eb4c1cf98
commit 355bcac6c7
4 changed files with 301 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
---
name: agent_launch_worktree
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func AgentLaunchWorktree(cfg WorktreeLaunchConfig) WorktreeLaunchResult"
description: "Crea un git worktree nuevo en una rama derivada de master y lanza `claude -p <prompt>` headless dentro de ese worktree, redirigiendo stdout+stderr a un log file. Devuelve inmediatamente con el PID — el proceso queda corriendo en background. Si `ResetIfExists=true` y la rama existe, borra rama + worktree previos (best-effort) antes de recrear. Si `claude` no esta en PATH, hace fallback a `echo` como stub para que los tests puedan correr sin el binario real. Usa exec.LookPath, NO hardcodea paths. Cleanup del worktree + branch se hace con `agent_cleanup_worktree_go_infra`."
tags: [agents, worktree, claude, git, headless]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: ["fmt", "os", "os/exec", "strings", "time"]
params:
- name: cfg
desc: "WorktreeLaunchConfig con RepoRoot (path absoluto al repo principal), Branch (ej. auto/0115-foo), WorktreePath (path absoluto donde crear el worktree), Prompt (texto pasado a claude -p), LogPath (archivo de log), Env opcional (env vars extra), SkipPerms (pasa --dangerously-skip-permissions), ResetIfExists (nuke previo de rama+worktree)."
output: "WorktreeLaunchResult con PID (claude process id), Branch/WorktreePath/LogPath (eco de inputs), StartedAt (unix seconds) y Error (string vacio en exito; mensaje en fallo). PID=0 cuando Error!='' . El campo Error usa string en vez de error nativo Go para poder serializarse a JSON desde agent_runner_api."
tested: true
tests:
- "creates worktree dir and branch off master"
- "ResetIfExists=true on existing branch+worktree succeeds"
- "returns Error when RepoRoot/Branch/WorktreePath missing"
test_file_path: "functions/infra/agent_launch_worktree_test.go"
file_path: "functions/infra/agent_launch_worktree.go"
---
## Ejemplo
```go
res := infra.AgentLaunchWorktree(infra.WorktreeLaunchConfig{
RepoRoot: "/home/lucas/fn_registry",
Branch: "auto/0115-worktree-launcher-fn",
WorktreePath: "/home/lucas/fn_registry/worktrees/0115-worktree-launcher-fn",
Prompt: "Implement issue 0115 — worktree launcher Go function",
LogPath: "/tmp/claude-0115.log",
SkipPerms: true,
ResetIfExists: true,
})
if res.Error != "" {
log.Fatal(res.Error)
}
fmt.Printf("claude PID=%d branch=%s log=%s\n", res.PID, res.Branch, res.LogPath)
// ... agente trabaja ...
infra.AgentCleanupWorktree(res.WorktreePath, res.Branch, "/home/lucas/fn_registry", res.PID)
```
## Cuando usarla
Cuando una app (`agent_runner_api`, `fn-orquestador`) o un script necesite lanzar Claude headless en un sandbox aislado: ramas `auto/<issue>` o `issue/<NNNN>`. Reemplaza el bash inline que vivia en `.claude/skills/parallel-fix-issues/` y en el agente `fn-orquestador`. Si lo que quieres es ejecutar Claude en foreground sin worktree, NO uses esta — usa un `exec.Command` directo.
## Gotchas
- **Spawn solo, no Wait**: la funcion hace `cmd.Start()` y vuelve. Si el caller necesita esperar al final, debe trackear el PID y hacer `syscall.Wait4` o consultar `/proc/<pid>`. Para cleanup ordenado, usa `agent_cleanup_worktree_go_infra`.
- **Master debe existir** en `RepoRoot` — la rama se crea con `git worktree add ... -b <branch> master`. Si tu repo usa `main`, fork la funcion o renombra la rama localmente.
- **`ResetIfExists` es best-effort**: si el worktree previo tiene cambios sin commitear o procesos atados, `git worktree remove --force` puede ignorar ciertos errores; siempre revisa el dir final.
- **Log file truncado**: cada launch reabre `LogPath` con `O_TRUNC`. Si quieres preservar el log de runs anteriores, rota el archivo antes de llamar.
- **Fallback `echo` stub** se activa cuando `exec.LookPath("claude")` falla; en ese caso el "proceso claude" imprime `STUB: claude not in PATH, prompt was: <prompt>` y termina inmediatamente. Util en CI/tests, no en produccion.
- **PID en Windows**: `syscall.Kill` no existe en Windows — `agent_cleanup_worktree` solo funciona en Linux/Darwin. Documentado alli.
- **Env**: los valores de `cfg.Env` se hacen append a `os.Environ()` — si quieres anular una var existente, en Go la ultima asignacion gana, asi que basta con incluirla en `cfg.Env`.