feat(infra): modelo de datos del meta-orquestador de flota (flow 0012)

Fase 1, piezas 1+2:
- ClaudeFleet + list_claude_fleet ganan DodContract/DodStatus/Role,
  leidos de goals/<sessionId>.json (.dod_contract/.dod_status/.role).
  Aditivo: fleetview sigue compilando.
- classify_fleet_termination (pura): clasifica el estado de terminacion
  de un agente (RECLAMA/MAL_LANZADO/DICE_TERMINADO/ESTANCADO/TRABAJANDO)
  con precedencia fija, para que un watcher sin LLM decida. 34 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
agent
2026-06-20 19:51:11 +02:00
parent b569561115
commit 28a53ee357
7 changed files with 315 additions and 40 deletions
@@ -0,0 +1,69 @@
package infra
// Termination labels returned by ClassifyFleetTermination. They describe the
// mechanical termination state of a Claude fleet agent so a cheap (LLM-free)
// watcher can decide what to do with it.
const (
// TerminationReclama means the agent is asking for human input and must be
// attended first, above any other consideration.
TerminationReclama = "RECLAMA"
// TerminationMalLanzado means the agent was launched without a DoD contract
// — no agent should run without an acceptance criterion.
TerminationMalLanzado = "MAL_LANZADO"
// TerminationDiceTerminado means the agent claims it is finished (idle and
// either phase "hecho" or its DoD status is "met").
TerminationDiceTerminado = "DICE_TERMINADO"
// TerminationEstancado means the agent is idle, not finished, and has been
// inactive at or beyond the stall threshold.
TerminationEstancado = "ESTANCADO"
// TerminationTrabajando means the agent is still working (busy, or idle
// recently below the stall threshold).
TerminationTrabajando = "TRABAJANDO"
)
// ClassifyFleetTermination mechanically classifies the termination state of a
// fleet agent. It is pure and deterministic: no I/O, no clock, no global state.
//
// Inputs:
// - status: process state from sessions.json — "idle" | "busy" | "waiting".
// - phase: work phase from goal.json — "investigando", "planificando",
// "haciendo", "testeando", "puliendo", "pendiente_revision", "preguntando",
// "bloqueado", "en_pausa", "hecho", "iterando" or "".
// - dodContract: fixed acceptance criterion ("" means none was defined).
// - dodStatus: "pending" | "met" | "failed" | "".
// - idleSeconds: seconds since the session's last activity.
// - stallThresholdSeconds: threshold to consider the agent stalled.
//
// Precedence (evaluated top to bottom; the first match wins):
// 1. RECLAMA — the agent is asking for human input (status "waiting" OR phase
// "preguntando"/"bloqueado"). This dominates everything, even a missing DoD
// contract: if it asks for input, that is the first thing to handle.
// 2. MAL_LANZADO — it does not reclaim input but has no DoD contract; no agent
// should run without one.
// 3. DICE_TERMINADO — idle AND (phase "hecho" OR dodStatus "met").
// 4. ESTANCADO — idle AND phase not "hecho" AND idleSeconds >= stall threshold.
// 5. TRABAJANDO — everything else (busy, or idle recently below the threshold).
func ClassifyFleetTermination(status, phase, dodContract, dodStatus string, idleSeconds, stallThresholdSeconds int) string {
// 1. RECLAMA dominates: a request for human input is handled first.
if status == "waiting" || phase == "preguntando" || phase == "bloqueado" {
return TerminationReclama
}
// 2. MAL_LANZADO: running without a DoD contract is invalid.
if dodContract == "" {
return TerminationMalLanzado
}
// 3. DICE_TERMINADO: idle and self-reporting completion.
if status == "idle" && (phase == "hecho" || dodStatus == "met") {
return TerminationDiceTerminado
}
// 4. ESTANCADO: idle, not finished, inactive at/beyond the stall threshold.
if status == "idle" && phase != "hecho" && idleSeconds >= stallThresholdSeconds {
return TerminationEstancado
}
// 5. TRABAJANDO: busy, or idle recently below the threshold.
return TerminationTrabajando
}