From e2b5ac56eb0c745cfdf1a112ae8c6232ebe33408 Mon Sep 17 00:00:00 2001 From: agent Date: Sun, 21 Jun 2026 12:56:14 +0200 Subject: [PATCH 1/2] fix(goal-tracker): eliminar regeneracion LLM (haiku) del dod movil en cada prompt El hook goal_refine.sh regeneraba el campo .dod del goal.json llamando a ask_llm.py (haiku) en background en CADA UserPromptSubmit de CADA sesion. Con muchas sesiones de la flota activas esto amplificaba el rate-limit compartido de la organizacion (una request API por turno por agente). El .dod movil no lo consume nadie: el parser de la flota (functions/infra/list_claude_fleet.go, struct goalFile/readGoal) solo lee goal/phase/emojis/rename/dod_contract/dod_status/role. El criterio que clasifica la flota (RECLAMA/DICE_TERMINADO/ESTANCADO) es dod_contract + dod_status, escrito por set_dod_contract.py sin LLM y consumido por ClassifyFleetTermination. Ese sistema queda intacto. Cambios: - goal_refine.sh: convertido en no-op (exit 0) documentado. - goal_tracker.sh: retirado el disparo de goal_refine + la acumulacion de .prompts que solo lo alimentaba; mensaje GOAL-TRACKER actualizado. El objetivo+DoD inicial los sigue generando goal_autogen.sh una sola vez por terminal (junto con goal/emojis, que si se usan). El usuario ajusta el DoD a mano con 'dod: ...'. Resultado: cero llamadas LLM por prompt. --- .claude/hooks/goal_refine.sh | 64 +++++++++++------------------------ .claude/hooks/goal_tracker.sh | 33 ++++++++---------- 2 files changed, 34 insertions(+), 63 deletions(-) diff --git a/.claude/hooks/goal_refine.sh b/.claude/hooks/goal_refine.sh index 02c7ae4..6a03c4e 100755 --- a/.claude/hooks/goal_refine.sh +++ b/.claude/hooks/goal_refine.sh @@ -1,48 +1,22 @@ #!/bin/bash -# Ajusta SOLO el DoD para mantenerlo coherente con los prompts del usuario hacia -# el objetivo. El OBJETIVO es fijo (identificativo de la terminal) y NUNCA se -# toca aqui. Lo lanza goal_tracker.sh en background (no bloquea). Usa ask_llm -# (haiku, API directa; nunca `claude -p`, ver regla llm_invocation.md). +# DESACTIVADO (2026-06-21): este hook regeneraba el campo `.dod` (movil) del +# goal.json llamando a un LLM (haiku via ask_llm.py) en CADA prompt de CADA +# sesion. Con muchas sesiones de la flota activas a la vez eso amplificaba el +# rate-limit compartido de la organizacion ("Server is temporarily limiting +# requests"). Una request API por turno por agente = coste innecesario. # -# Args: - -SID="$1" -F="$2" - -[ -f "$F" ] || exit 0 -PY="$HOME/fn_registry/python/.venv/bin/python3" -ASK="$HOME/fn_registry/python/functions/core/ask_llm.py" -[ -x "$PY" ] || exit 0 -[ -f "$ASK" ] || exit 0 - -GOAL=$(jq -r '.goal // ""' "$F" 2>/dev/null) -[ -z "$GOAL" ] && exit 0 -DOD=$(jq -r '.dod // ""' "$F" 2>/dev/null) -PROMPTS=$(jq -r '(.prompts // []) | to_entries | map("\(.key+1). \(.value)") | join("\n")' "$F" 2>/dev/null) -[ -z "$PROMPTS" ] && exit 0 - -SYS="Dado un OBJETIVO fijo de una terminal de trabajo y los prompts del usuario, define o ajusta el DoD (definition of done): la condicion concreta de 'terminado' para ese objetivo, coherente con lo que el usuario va pidiendo. El OBJETIVO no se toca, solo el DoD. Responde SOLO JSON en una linea, sin markdown: {\"dod\":\"...\"}. dod en espanol, breve, maximo 9 palabras. Si el DoD actual ya es adecuado, devuelvelo igual." - -PROMPT="OBJETIVO (fijo, no lo cambies): ${GOAL} -DoD ACTUAL: ${DOD} - -PROMPTS DEL USUARIO (orden cronologico): -${PROMPTS} - -Responde el JSON con el DoD:" - -RAW=$("$PY" "$ASK" --system "$SYS" "$PROMPT" 2>/dev/null) -[ -z "$RAW" ] && exit 0 -JSON=$(printf '%s' "$RAW" | tr '\n' ' ' | grep -o '{[^{}]*}' | head -1) -[ -z "$JSON" ] && exit 0 - -ND=$(printf '%s' "$JSON" | jq -r '.dod // ""' 2>/dev/null) -[ -z "$ND" ] && exit 0 - -TMP="${F}.tmp.$$" -if jq --arg d "$ND" '.dod=$d' "$F" > "$TMP" 2>/dev/null; then - mv "$TMP" "$F" -else - rm -f "$TMP" -fi +# El `.dod` movil NO lo consume nadie: el parser de la flota +# (functions/infra/list_claude_fleet.go, struct goalFile/readGoal) solo lee +# goal/phase/emojis/rename/dod_contract/dod_status/role; ignora `.dod` por +# completo. El criterio de aceptacion real que clasifica la flota es +# `dod_contract` + `dod_status`, escrito por set_dod_contract.py (sin LLM) y +# consumido por ClassifyFleetTermination. Ese sistema queda intacto. +# +# Por tanto la regeneracion del `.dod` movil con haiku se elimina por completo: +# cero llamadas LLM por prompt. El objetivo+DoD inicial los sigue generando +# goal_autogen.sh una sola vez por terminal (junto con goal/emojis, que si se +# usan); el usuario puede ajustar el DoD a mano con "dod: ...". +# +# Se conserva el archivo como no-op para no romper ningun disparador historico +# (defensa en profundidad). El disparo desde goal_tracker.sh tambien se retiro. exit 0 diff --git a/.claude/hooks/goal_tracker.sh b/.claude/hooks/goal_tracker.sh index 3fc8e78..0e6c225 100755 --- a/.claude/hooks/goal_tracker.sh +++ b/.claude/hooks/goal_tracker.sh @@ -83,15 +83,22 @@ case "$PROMPT_TRIM" in block "⏸️ Fase marcada en pausa." ;; esac -# --- prompt NORMAL: pasa al agente + acumula para refinar el DoD + estado --- -# Distinguimos tres situaciones por el flag .provisional del goal file: +# --- prompt NORMAL: pasa al agente + estado --- +# Distinguimos dos situaciones por el flag .provisional del goal file: # - no existe el archivo -> primer prompt: ponemos objetivo PROVISIONAL = tu -# texto + lanzamos autogen (haiku) que lo definira. +# texto + lanzamos autogen (haiku, UNA sola vez) +# que lo definira. # - existe pero .provisional -> autogen aun no termino (o fallo): conservamos el -# provisional, acumulamos el prompt y relanzamos -# autogen (idempotente, self-healing). -# - existe y NO provisional -> objetivo definitivo: acumulamos prompt y solo -# refinamos el DoD; el objetivo no se toca. +# provisional y relanzamos autogen (idempotente, +# self-healing). +# - existe y NO provisional -> objetivo definitivo: solo mostramos estado. +# +# NOTA (2026-06-21): el campo `.dod` movil YA NO se regenera con LLM en cada +# prompt. goal_refine.sh esta desactivado (era una request haiku por turno por +# sesion -> amplificaba el rate-limit compartido de la organizacion). El `.dod` +# movil no lo consume nadie; el criterio que clasifica la flota es `dod_contract` +# + `dod_status` (set_dod_contract.py, sin LLM). El DoD inicial lo fija autogen +# una vez; el usuario lo ajusta a mano con "dod: ...". PROV="false" [ -f "$F" ] && PROV=$(jq -r '.provisional // false' "$F" 2>/dev/null) @@ -99,17 +106,7 @@ if [ -f "$F" ] && [ "$PROV" != "true" ]; then G=$(jq -r '.goal // ""' "$F" 2>/dev/null) P=$(jq -r '.phase // ""' "$F" 2>/dev/null) D=$(jq -r '.dod // ""' "$F" 2>/dev/null) - # Acumular el prompt y ajustar SOLO el DoD (background). El objetivo no cambia. - if [ "${#PROMPT_TRIM}" -ge 12 ]; then - TMP="${F}.tmp.$$" - if jq --arg p "$PROMPT_TRIM" '.prompts = ((.prompts // []) + [$p])[-12:]' "$F" > "$TMP" 2>/dev/null; then - mv "$TMP" "$F" - else - rm -f "$TMP" - fi - nohup bash "$HOME/.claude/hooks/goal_refine.sh" "$SID" "$F" >/dev/null 2>&1 & - fi - echo "GOAL-TRACKER: file=$F | goal=\"$G\" dod=\"$D\" phase=\"$P\". El objetivo es fijo (identificativo de la terminal, NO lo cambies). El DoD se ajusta solo con los prompts; la fase la mantienen los hooks (PostToolUse=activo, Stop=reposo) — NO la escribas. Comandos meta del usuario (no los uses tu): objetivo:/dod:/pausa." + echo "GOAL-TRACKER: file=$F | goal=\"$G\" dod=\"$D\" phase=\"$P\". El objetivo es fijo (identificativo de la terminal, NO lo cambies). El DoD inicial lo fija el autogen una vez (sin LLM por prompt); el usuario lo ajusta con \"dod: ...\" — NO lo regeneres tu. La fase la mantienen los hooks (PostToolUse=activo, Stop=reposo) — NO la escribas. Comandos meta del usuario (no los uses tu): objetivo:/dod:/pausa." else # Sin objetivo definitivo todavia. Mostramos de inmediato un objetivo PROVISIONAL # igual a tu propio texto (truncado), para que el statusline no quede vacio From d4640a06609f8f636b2d5d61947a28dd454ef9e2 Mon Sep 17 00:00:00 2001 From: integrador Date: Sun, 21 Jun 2026 14:59:15 +0200 Subject: [PATCH 2/2] fix(goal): poblar goal de ejecutores spawneados (statusline/FleetView sin objetivo) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spawn_fleet_agent pre-crea ~/.claude/goals/.json con solo {role, parent_orchestrator} antes del primer prompt. goal_tracker.sh usaba 'el archivo existe' como proxy de 'objetivo definitivo', así que para esos ejecutores nunca lanzaba goal_autogen: el goal quedaba vacío para siempre y el statusline (LINE0) y FleetView mostraban '(sin objetivo)'. Fix: 'definitivo' ahora exige .goal NO vacío (no solo que el archivo exista). Cuando el archivo existe pero sin goal (ejecutor spawneado), se fija un goal provisional PRESERVANDO role/parent_orchestrator y se lanza autogen, que lo pisa con el definitivo. No regresiona el caso definitivo ni el primer-prompt sin archivo (verificado con 3 casos). --- .claude/hooks/goal_tracker.sh | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.claude/hooks/goal_tracker.sh b/.claude/hooks/goal_tracker.sh index 0e6c225..12b3daf 100755 --- a/.claude/hooks/goal_tracker.sh +++ b/.claude/hooks/goal_tracker.sh @@ -100,9 +100,18 @@ esac # + `dod_status` (set_dod_contract.py, sin LLM). El DoD inicial lo fija autogen # una vez; el usuario lo ajusta a mano con "dod: ...". PROV="false" -[ -f "$F" ] && PROV=$(jq -r '.provisional // false' "$F" 2>/dev/null) +GOAL_NOW="" +if [ -f "$F" ]; then + PROV=$(jq -r '.provisional // false' "$F" 2>/dev/null) + GOAL_NOW=$(jq -r '.goal // ""' "$F" 2>/dev/null) +fi -if [ -f "$F" ] && [ "$PROV" != "true" ]; then +# "Objetivo definitivo" = archivo con goal NO vacio y no provisional. El check de +# goal no vacio es clave para los ejecutores lanzados por spawn_fleet_agent: su +# goal.json se PRE-CREA con solo {role, parent_orchestrator} (sin goal). Sin este +# guard, el hook tomaria ese archivo como objetivo definitivo y nunca lanzaria +# autogen, dejando el goal vacio para siempre (statusline y FleetView sin objetivo). +if [ -f "$F" ] && [ "$PROV" != "true" ] && [ -n "$GOAL_NOW" ]; then G=$(jq -r '.goal // ""' "$F" 2>/dev/null) P=$(jq -r '.phase // ""' "$F" 2>/dev/null) D=$(jq -r '.dod // ""' "$F" 2>/dev/null) @@ -114,11 +123,17 @@ else # con el definitivo al terminar (su guard respeta .provisional). if [ "${#PROMPT_TRIM}" -ge 12 ]; then TMP="${F}.tmp.$$" - if [ -f "$F" ]; then - # Ya habia provisional: conserva su goal, solo acumula el prompt. + PROV_GOAL=$(printf '%s' "$PROMPT_TRIM" | head -c 70) + if [ -n "$GOAL_NOW" ]; then + # Ya habia goal provisional: conserva su goal, solo acumula el prompt. jq --arg p "$PROMPT_TRIM" '.prompts = ((.prompts // []) + [$p])[-12:]' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F" || rm -f "$TMP" + elif [ -f "$F" ]; then + # Archivo PRE-CREADO por spawn_fleet_agent ({role, parent_orchestrator}) + # sin goal: fija el provisional PRESERVANDO los campos existentes (role, + # parent_orchestrator) y deja que autogen lo pise con el definitivo. + jq --arg g "$PROV_GOAL" --arg p "$PROMPT_TRIM" \ + '. + {goal:$g, phase:"planificando", history:["planificando"], prompts:[$p], provisional:true}' "$F" > "$TMP" 2>/dev/null && mv "$TMP" "$F" || rm -f "$TMP" else - PROV_GOAL=$(printf '%s' "$PROMPT_TRIM" | head -c 70) jq -n --arg g "$PROV_GOAL" --arg p "$PROMPT_TRIM" \ '{goal:$g, phase:"planificando", history:["planificando"], prompts:[$p], provisional:true}' > "$TMP" 2>/dev/null && mv "$TMP" "$F" || rm -f "$TMP" fi