Rediseño tool results + compactación por step + integración Docker
- Tool results completos en conversación (como Claude Code/Cursor) en vez de resúmenes en system prompt - Parser multi-tool: trackea tool calls por tool_call_id para OpenAI streaming interleaved - Deduplicación por fingerprint + detección de loop cuando todos los calls de un step son duplicados - Compactación inteligente por step: el orquestador decide cuándo comprimir steps anteriores (cambio de agente o >3 steps) - stdio.js lee URLs del .acai como fallback (local_web_url, local_forge_host) - Buffer MCP aumentado a 1MB para respuestas grandes - Dockerfile adaptado para build context desde raíz del proyecto Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -62,10 +62,15 @@ class ContextEngine:
|
||||
session: SessionState,
|
||||
agent: AgentProfile,
|
||||
artifacts: list[ArtifactSummary] | None = None,
|
||||
working_items: list[dict[str, Any]] | None = None,
|
||||
conversation: list[dict[str, Any]] | None = None,
|
||||
extra_instructions: str = "",
|
||||
) -> ContextPackage:
|
||||
"""Build a full ContextPackage for the given agent and session."""
|
||||
"""Build a full ContextPackage for the given agent and session.
|
||||
|
||||
The conversation parameter contains real assistant/tool messages
|
||||
with complete tool results. These go into the messages array,
|
||||
not the system prompt — like professional agentic tools.
|
||||
"""
|
||||
|
||||
sections: list[ContextSection] = []
|
||||
allowed = set(agent.context_sections)
|
||||
@@ -88,28 +93,18 @@ class ContextEngine:
|
||||
if "task_state" in allowed and session.task_history:
|
||||
sections.append(self._build_task_history(session))
|
||||
|
||||
# 5. Task state — current task
|
||||
# 5. Task state — current task (includes compacted previous steps)
|
||||
if "task_state" in allowed and session.current_task:
|
||||
sections.append(self._build_task_state(session.current_task))
|
||||
|
||||
# 6. Artifact memory — summarised, never raw (only current task's)
|
||||
if "artifact_memory" in allowed and artifacts:
|
||||
sections.append(self._build_artifact_memory(artifacts))
|
||||
|
||||
# 6. Working context — recent relevant items
|
||||
if "working_context" in allowed:
|
||||
sections.append(
|
||||
self._build_working_context(working_items or [], extra_instructions)
|
||||
)
|
||||
|
||||
# Compact to fit budget
|
||||
sections = self.compactor.compact_sections(sections)
|
||||
|
||||
# Assemble system prompt from sections
|
||||
system_prompt = self._assemble_system_prompt(sections)
|
||||
|
||||
# Build messages (just user message — no chat history)
|
||||
messages = self._build_messages(session)
|
||||
# Build messages with real conversation history
|
||||
messages = self._build_messages(session, conversation)
|
||||
|
||||
total_tokens = estimate_tokens(system_prompt) + sum(
|
||||
estimate_tokens(m.get("content", "")) for m in messages
|
||||
@@ -133,6 +128,7 @@ class ContextEngine:
|
||||
"preview": s.content[:150].replace("\n", " "),
|
||||
})
|
||||
|
||||
conv_len = len(conversation) if conversation else 0
|
||||
debug_entry = {
|
||||
"timestamp": time.time(),
|
||||
"agent": agent.role.value,
|
||||
@@ -144,7 +140,7 @@ class ContextEngine:
|
||||
"system_prompt_tokens": estimate_tokens(system_prompt),
|
||||
"user_message_preview": messages[0]["content"][:200] if messages else "",
|
||||
"artifacts_count": len(artifacts) if artifacts else 0,
|
||||
"working_items_count": len(working_items) if working_items else 0,
|
||||
"conversation_messages": conv_len,
|
||||
}
|
||||
|
||||
history = self._history[session.session_id]
|
||||
@@ -153,19 +149,14 @@ class ContextEngine:
|
||||
self._history[session.session_id] = history[-self._max_history:]
|
||||
|
||||
logger.info(
|
||||
"Context built for [%s/%s] — %d sections, ~%d tokens, artifacts=%d, working_items=%d",
|
||||
"Context built for [%s/%s] — %d sections, ~%d tokens, artifacts=%d, conversation=%d msgs",
|
||||
session.session_id[:8],
|
||||
agent.role.value,
|
||||
len(sections),
|
||||
total_tokens,
|
||||
len(artifacts) if artifacts else 0,
|
||||
len(working_items) if working_items else 0,
|
||||
conv_len,
|
||||
)
|
||||
for s in section_summary:
|
||||
logger.debug(
|
||||
" Section [%s] prio=%d tokens=%d chars=%d",
|
||||
s["type"], s["priority"], s["tokens"], s["chars"],
|
||||
)
|
||||
|
||||
return package
|
||||
|
||||
@@ -236,10 +227,11 @@ class ContextEngine:
|
||||
[
|
||||
"",
|
||||
"## Contrato de Contexto",
|
||||
"- NUNCA recibirás salidas crudas de herramientas en tu contexto.",
|
||||
"- Los resultados de herramientas se resumen como artefactos.",
|
||||
"- Solicita rehidratación si necesitas el contenido completo.",
|
||||
"- Los resultados de herramientas se incluyen completos en la conversación.",
|
||||
"- Los steps anteriores pueden estar compactados como resúmenes.",
|
||||
"- Mantén las respuestas enfocadas en el paso actual.",
|
||||
"- Si ya tienes la información necesaria, genera tu respuesta final.",
|
||||
"- NO repitas llamadas a herramientas con los mismos argumentos.",
|
||||
"- Responde SIEMPRE en español.",
|
||||
]
|
||||
)
|
||||
@@ -451,6 +443,14 @@ class ContextEngine:
|
||||
for c in task.constraints:
|
||||
lines.append(f"- {c}")
|
||||
|
||||
# Show compacted previous steps results
|
||||
compacted_steps = [s for s in task.plan if s.compacted and s.result_summary]
|
||||
if compacted_steps:
|
||||
lines.append("")
|
||||
lines.append("## Previous Steps (compacted)")
|
||||
for step in compacted_steps:
|
||||
lines.append(f"- [{step.agent_role}] {step.description}: {step.result_summary[:300]}")
|
||||
|
||||
# Show plan overview (compact)
|
||||
if task.plan:
|
||||
lines.append("")
|
||||
@@ -458,8 +458,9 @@ class ContextEngine:
|
||||
for i, step in enumerate(task.plan):
|
||||
marker = "→" if i == task.current_step_index else "·"
|
||||
status_label = step.status.value
|
||||
compacted_label = " (compacted)" if step.compacted else ""
|
||||
lines.append(
|
||||
f" {marker} Step {i + 1} [{status_label}]: {step.description}"
|
||||
f" {marker} Step {i + 1} [{status_label}{compacted_label}]: {step.description}"
|
||||
)
|
||||
|
||||
content = "\n".join(lines)
|
||||
@@ -483,26 +484,6 @@ class ContextEngine:
|
||||
token_estimate=estimate_tokens(content),
|
||||
)
|
||||
|
||||
def _build_working_context(
|
||||
self,
|
||||
items: list[dict[str, Any]],
|
||||
extra_instructions: str,
|
||||
) -> ContextSection:
|
||||
lines = ["# Working Context"]
|
||||
if extra_instructions:
|
||||
lines.append(f"\n{extra_instructions}")
|
||||
for item in items[: settings.working_context_max_items]:
|
||||
role = item.get("role", "info")
|
||||
content_val = item.get("content", "")
|
||||
lines.append(f"[{role}] {content_val}")
|
||||
content = "\n".join(lines)
|
||||
return ContextSection(
|
||||
section_type=ContextSectionType.WORKING_CONTEXT,
|
||||
content=content,
|
||||
priority=30,
|
||||
token_estimate=estimate_tokens(content),
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Assembly
|
||||
# ------------------------------------------------------------------
|
||||
@@ -510,14 +491,11 @@ class ContextEngine:
|
||||
def _assemble_system_prompt(self, sections: list[ContextSection]) -> str:
|
||||
"""Combine sections into a single system prompt string."""
|
||||
parts: list[str] = []
|
||||
# Order: rules → profile → task → artifacts → working
|
||||
order = [
|
||||
ContextSectionType.IMMUTABLE_RULES,
|
||||
ContextSectionType.PROJECT_PROFILE,
|
||||
ContextSectionType.KNOWLEDGE_BASE,
|
||||
ContextSectionType.TASK_STATE,
|
||||
ContextSectionType.ARTIFACT_MEMORY,
|
||||
ContextSectionType.WORKING_CONTEXT,
|
||||
]
|
||||
section_map: dict[ContextSectionType, ContextSection] = {
|
||||
s.section_type: s for s in sections
|
||||
@@ -527,11 +505,15 @@ class ContextEngine:
|
||||
parts.append(section_map[st].content)
|
||||
return "\n\n---\n\n".join(parts)
|
||||
|
||||
def _build_messages(self, session: SessionState) -> list[dict[str, Any]]:
|
||||
"""Build the messages array. We do NOT include chat history.
|
||||
def _build_messages(
|
||||
self,
|
||||
session: SessionState,
|
||||
conversation: list[dict[str, Any]] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Build the messages array with real conversation history.
|
||||
|
||||
The user message is the current task objective (or a sentinel
|
||||
if no task is active).
|
||||
Includes the user objective message followed by the full
|
||||
assistant/tool conversation — like professional agentic tools.
|
||||
"""
|
||||
if session.current_task:
|
||||
step = session.current_task.current_step()
|
||||
@@ -545,4 +527,10 @@ class ContextEngine:
|
||||
else:
|
||||
user_content = "Awaiting task assignment."
|
||||
|
||||
return [{"role": "user", "content": user_content}]
|
||||
messages: list[dict[str, Any]] = [{"role": "user", "content": user_content}]
|
||||
|
||||
# Append real conversation (assistant messages + tool results)
|
||||
if conversation:
|
||||
messages.extend(conversation)
|
||||
|
||||
return messages
|
||||
|
||||
Reference in New Issue
Block a user