Files
fn_registry/python/functions/infra/set_dod_contract.py
T
agent 9365def3dd feat: cerebro reactivo del meta-orquestador (flow 0012, fase 2)
Primitivas (python/functions/infra):
- drain_fleet_events: consume la cola del watcher (~/.claude/fleet/
  events.jsonl) desde un cursor, agrupa por clasificacion, marca
  urgentes. 7 tests.
- set_dod_contract: escribe el DoD-contrato fijo (dod_contract/dod_status)
  en el goal.json de un agente sin pisar el resto (escritura atomica).
  5 tests.

Skill /orquestador evolucionado (sin romper lo existente): vigila la
flota por su DoD (no por 'esta vivo'). Nueva seccion 'Consumo de la cola
de la flota': DoD-contrato obligatorio al lanzar, drenar la cola,
politicas por clasificacion (RECLAMA escala / DICE_TERMINADO verifica /
ESTANCADO nudge / MAL_LANZADO re-DoD), verificador independiente del
ejecutor (lee el report vs dod_contract), splitter con tope de fan-out,
y cadencia (drain al actuar + heartbeat).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 20:19:26 +02:00

80 lines
2.7 KiB
Python

"""Escribe un DoD-contrato fijo y su estado en el sidecar goal.json de una sesion Claude."""
import json
import os
_VALID_STATUS = ("pending", "met", "failed")
def set_dod_contract(
session_id: str,
contract: str,
status: str = "pending",
goals_dir: str | None = None,
) -> dict:
"""Escribe el DoD-contrato y su estado en el goal.json de una sesion Claude.
Lee el sidecar `<goals_dir>/<session_id>.json` si existe, preservando
TODOS sus campos (goal, phase, dod, history, prompts, etc.), y actualiza
solo las claves `dod_contract` y `dod_status`. La escritura es atomica
(tmp + os.replace) para no corromper el archivo si el proceso muere a mitad.
Args:
session_id: ID de la sesion Claude. Da nombre al archivo
`<session_id>.json` dentro de goals_dir.
contract: Criterio de aceptacion estable contra el que se evalua la
terminacion del agente. No puede ser vacio.
status: Estado del contrato. Uno de "pending", "met" o "failed".
goals_dir: Directorio de sidecars de goal. Por defecto
`~/.claude/goals`.
Returns:
dict con session_id, path, dod_contract, dod_status y written.
Raises:
ValueError: si contract es vacio o status no es valido. No escribe nada.
"""
if not contract or not contract.strip():
raise ValueError("contract no puede ser vacio: un DoD-contrato vacio no tiene sentido")
if status not in _VALID_STATUS:
raise ValueError(
f"status invalido: {status!r}. Debe ser uno de {_VALID_STATUS}"
)
if goals_dir is None:
goals_dir = os.path.join(os.path.expanduser("~"), ".claude", "goals")
os.makedirs(goals_dir, exist_ok=True)
path = os.path.join(goals_dir, f"{session_id}.json")
data: dict = {}
if os.path.exists(path):
try:
with open(path, "r", encoding="utf-8") as f:
loaded = json.load(f)
if isinstance(loaded, dict):
data = loaded
except (json.JSONDecodeError, OSError):
# Archivo corrupto o ilegible: no se pierde lo nuevo, se parte limpio.
data = {}
# Solo se tocan estas dos claves; el resto del dict se preserva intacto.
data["dod_contract"] = contract
data["dod_status"] = status
tmp_path = f"{path}.tmp"
with open(tmp_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
f.write("\n")
f.flush()
os.fsync(f.fileno())
os.replace(tmp_path, path)
return {
"session_id": session_id,
"path": path,
"dod_contract": contract,
"dod_status": status,
"written": True,
}