--- name: classify_fleet_termination kind: function lang: go domain: infra version: "1.0.0" purity: pure signature: "func ClassifyFleetTermination(status, phase, dodContract, dodStatus string, idleSeconds, stallThresholdSeconds int) string" description: "Clasifica MECANICAMENTE el estado de terminacion de un agente Claude de la flota para que un watcher barato sin LLM decida que hacer. Pura y determinista. Devuelve una de RECLAMA, MAL_LANZADO, DICE_TERMINADO, ESTANCADO o TRABAJANDO segun precedencia fija: RECLAMA (pide input humano) manda sobre todo, luego MAL_LANZADO (sin DoD-contrato), luego DICE_TERMINADO, ESTANCADO y TRABAJANDO." tags: [fleet, claude-fleet, classification, watcher, termination, orchestrator, pure, infra, orchestration] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "" imports: [] params: - name: status desc: "estado del proceso Claude leido de sessions.json: idle | busy | waiting" - name: phase desc: "fase de trabajo del goal.json: investigando | planificando | haciendo | testeando | puliendo | pendiente_revision | preguntando | bloqueado | en_pausa | hecho | iterando | (vacio)" - name: dodContract desc: "criterio de aceptacion fijo del agente; cadena vacia significa que no se definio DoD-contrato (agente mal lanzado)" - name: dodStatus desc: "estado de cumplimiento del DoD: pending | met | failed | (vacio)" - name: idleSeconds desc: "segundos transcurridos desde la ultima actividad de la sesion" - name: stallThresholdSeconds desc: "umbral en segundos a partir del cual un agente idle no terminado se considera ESTANCADO (comparacion >=, inclusiva)" output: "una etiqueta string: RECLAMA | MAL_LANZADO | DICE_TERMINADO | ESTANCADO | TRABAJANDO (constantes exportadas TerminationReclama, TerminationMalLanzado, TerminationDiceTerminado, TerminationEstancado, TerminationTrabajando)" tested: true tests: - "waiting reclama input" - "phase preguntando reclama" - "phase bloqueado reclama" - "waiting manda aunque sin dodContract" - "preguntando manda aunque sin dodContract" - "bloqueado manda aunque idle estancado" - "sin dodContract busy" - "sin dodContract idle reciente" - "sin dodContract idle estancado" - "sin dodContract phase hecho" - "sin dodContract dodStatus met" - "idle phase hecho" - "idle dodStatus met" - "idle hecho y met" - "idle met aunque estancado por tiempo" - "idle hecho aunque estancado por tiempo" - "idle no hecho en umbral exacto" - "idle no hecho por encima del umbral" - "idle iterando estancado" - "idle dodStatus failed estancado" - "idle en_pausa estancado" - "busy trabajando" - "busy aunque idleSeconds alto" - "idle reciente bajo umbral" - "idle reciente cero segundos" - "idle no hecho justo bajo umbral" - "busy phase vacia con dodContract" - "umbral cero idle no hecho => estancado" - "idle hecho con umbral cero => dice terminado" - "dodStatus met con status busy NO termina" - "phase hecho con status busy NO termina" - "idle pendiente_revision bajo umbral trabajando" - "idle pendiente_revision sobre umbral estancado" - "waiting con dodStatus met sigue reclamando" test_file_path: "functions/infra/classify_fleet_termination_test.go" file_path: "functions/infra/classify_fleet_termination.go" --- ## Ejemplo ```go // Agente idle que dice estar terminado. label := ClassifyFleetTermination("idle", "hecho", "tests verdes + indexado", "met", 30, 600) // label == "DICE_TERMINADO" (== TerminationDiceTerminado) // Agente idle, no terminado, parado 12 minutos con umbral de 10 minutos. label = ClassifyFleetTermination("idle", "haciendo", "tests verdes", "pending", 720, 600) // label == "ESTANCADO" // Agente que reclama input humano: manda sobre todo, aunque le falte DoD. label = ClassifyFleetTermination("waiting", "haciendo", "", "", 5, 600) // label == "RECLAMA" switch label { case TerminationReclama: // notificar al humano case TerminationMalLanzado: // matar y relanzar con DoD-contrato case TerminationDiceTerminado: // pasar a verificacion de DoD case TerminationEstancado: // empujar / reiniciar / escalar case TerminationTrabajando: // dejar trabajar } ``` ## Cuando usarla Usala en el watcher del meta-orquestador de flota (dev/flows/0012-fleet-orchestrator-dod.md) cuando barras cada agente Claude y necesites decidir mecanicamente, sin gastar LLM, en que cubo cae (reclama input / mal lanzado / dice terminado / estancado / trabajando) a partir de sessions.json + goal.json. Es el paso de triaje barato antes de cualquier accion costosa. ## Gotchas - **Precedencia estricta de arriba a abajo.** RECLAMA gana siempre, incluso si el agente no tiene `dodContract` o ya esta idle y estancado. Si tu logica espera que MAL_LANZADO domine, esta funcion no hace eso a proposito: un agente que pide input se atiende primero. - **El umbral es inclusivo (`>=`).** `idleSeconds == stallThresholdSeconds` ya cuenta como ESTANCADO. Con `stallThresholdSeconds == 0` cualquier agente idle no terminado es ESTANCADO al instante. - **`status == "busy"` nunca termina ni se estanca.** Aunque `dodStatus == "met"` o `phase == "hecho"`, si el proceso esta busy se clasifica como TRABAJANDO (la condicion de terminacion/estancamiento exige idle). - **Strings exactos, case-sensitive.** Valores fuera de los esperados (ej. "Idle", "WAITING", una phase desconocida) caen a TRABAJANDO salvo que disparen otra rama. Normaliza las entradas antes de llamar si tus fuentes no garantizan el casing.