Compactor: garantizar emparejamiento tool_use/tool_result (sesiones largas bloqueadas)

Las sesiones largas con DeepSeek quedaban bloqueadas permanentemente con
400 "Messages with role 'tool' must be a response to a preceding message
with 'tool_calls'": el paso de ultimo recurso del compactor colapsaba
assistants con tool_use a un string placeholder dejando huerfanos los
tool_result del user siguiente.

- compactor: paso de ultimo recurso pair-aware + _enforce_tool_pairing
  como invariante final (matching por IDs, ambas direcciones, repara
  tambien historiales ya corruptos persistidos).
- openai_adapter: _repair_tool_sequence como guard defensivo del contrato
  del proveedor (tool huerfano -> user; tool_call sin respuesta -> fuera),
  con warning para detectar regresiones.
- recent_messages: trim por presupuesto de tokens al persistir
  (AGENTIC_RECENT_MESSAGES_MAX_TOKENS, default 60k) sin cortar pares;
  cierra el crecimiento sin limite que empujaba al paso destructivo.
- tests/test_tool_pairing_real.py: 23 tests que importan el codigo REAL
  (a diferencia de los tests standalone existentes). Suite completa: 92 ok.

Verificado offline contra los recent_messages reales de la sesion
bloqueada en prod: 0 violaciones con presupuesto normal y agresivo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jordan Diaz
2026-06-10 19:08:53 +00:00
parent 43337e8554
commit 79ec267aa6
6 changed files with 1020 additions and 8 deletions

View File

@@ -102,6 +102,10 @@ class Settings(BaseSettings):
conversation_recent_raw_limit: int = 2
task_history_max_entries: int = 20
task_history_max_tokens: int = 1500
# Presupuesto de tokens para la ventana de recent_messages persistida en
# sesion. Sin esto crece sin limite y empuja al compactor a su paso
# destructivo (colapsar bloques perdiendo tool_use ids). 0 = sin limite.
recent_messages_max_tokens: int = 60_000
# --- MCP ---
mcp_config_path: str = "" # Path to mcp.json; empty = legacy single-server mode