From 2997622b4dde16f71c35320edd9f1e410968f492 Mon Sep 17 00:00:00 2001 From: Jordan Date: Thu, 2 Apr 2026 00:34:06 +0100 Subject: [PATCH] resumen de artifacts --- src/context/engine.py | 44 +++++++++++++++++++++++-- src/models/session.py | 1 + src/orchestrator/engine.py | 67 +++++++++++++++++++++++++++++++++++++- 3 files changed, 109 insertions(+), 3 deletions(-) diff --git a/src/context/engine.py b/src/context/engine.py index 49de0a6..9f7740f 100644 --- a/src/context/engine.py +++ b/src/context/engine.py @@ -84,11 +84,15 @@ class ContextEngine: if kb_section: sections.append(kb_section) - # 4. Task state + # 4. Task history — compact summaries of past tasks in this session + if "task_state" in allowed and session.task_history: + sections.append(self._build_task_history(session)) + + # 5. Task state — current task if "task_state" in allowed and session.current_task: sections.append(self._build_task_state(session.current_task)) - # 5. Artifact memory — summarised, never raw + # 6. Artifact memory — summarised, never raw (only current task's) if "artifact_memory" in allowed and artifacts: sections.append(self._build_artifact_memory(artifacts)) @@ -379,6 +383,42 @@ class ContextEngine: parts.extend(session.current_task.facts_extracted[-5:]) return " ".join(parts) + def _build_task_history(self, session: SessionState) -> ContextSection: + """Build a compact summary of past tasks in this session. + + Each completed task is ~50 tokens instead of hundreds. + The agent retains awareness of what was done before. + """ + lines = [ + "# Session History", + f"_{len(session.task_history)} previous task(s) in this session_", + "", + ] + + for i, entry in enumerate(session.task_history): + status = entry.get("status", "?") + objective = entry.get("objective", "")[:100] + summary = entry.get("summary", "")[:150] + facts = entry.get("facts", []) + + lines.append(f"**Task {i + 1}** [{status}]: {objective}") + if summary: + lines.append(f" Result: {summary}") + if facts: + lines.append(f" Facts: {'; '.join(facts[:5])}") + review = entry.get("review", "") + if review: + lines.append(f" Review: {review[:100]}") + lines.append("") + + content = "\n".join(lines) + return ContextSection( + section_type=ContextSectionType.TASK_STATE, + content=content, + priority=55, # Below knowledge (60), above artifacts (50) + token_estimate=estimate_tokens(content), + ) + def _build_task_state(self, task: TaskState) -> ContextSection: lines = [ "# Current Task", diff --git a/src/models/session.py b/src/models/session.py index de18a20..1a9a406 100644 --- a/src/models/session.py +++ b/src/models/session.py @@ -85,6 +85,7 @@ class SessionState(BaseModel): immutable_rules: list[str] = Field(default_factory=list) current_task: TaskState | None = None completed_tasks: list[str] = Field(default_factory=list) + task_history: list[dict[str, Any]] = Field(default_factory=list) # Compact summaries of past tasks turn_count: int = 0 created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) diff --git a/src/orchestrator/engine.py b/src/orchestrator/engine.py index c5b23f1..a14e83e 100644 --- a/src/orchestrator/engine.py +++ b/src/orchestrator/engine.py @@ -209,7 +209,10 @@ class OrchestratorEngine: logger.error("Review failed: %s", e) review_result = {"content": f"Review skipped due to error: {e}"} - # 5. Complete — session ALWAYS returns to idle + # 5. Compact task into history before completing + await self._compact_task_to_history(session, task, results, review_result) + + # 6. Complete — session ALWAYS returns to idle session.complete_task() final_content = self._assemble_response(results, review_result) @@ -258,6 +261,68 @@ class OrchestratorEngine: # Internals # ------------------------------------------------------------------ + async def _compact_task_to_history( + self, + session: SessionState, + task: TaskState, + results: list[dict[str, Any]], + review_result: dict[str, Any], + ) -> None: + """Compact a completed task into a minimal history entry. + + This is critical for long sessions: instead of keeping all + artifacts and facts from every task, we compress each completed + task into a ~200 token summary that preserves: + - What was done (objective) + - What was produced (file changes, modules created) + - Key facts learned + - Issues found by reviewer + """ + # Collect all artifact summaries from this task + artifacts = await self.memory.list_artifacts(session.session_id) + task_artifacts = [a for a in artifacts if a.task_id == task.task_id] + + # Build compact summary + step_summaries = [] + for step in task.plan: + if step.result_summary: + step_summaries.append(f"{step.agent_role}: {step.result_summary[:100]}") + + tools_used = set() + for step in task.plan: + tools_used.update(step.tools_used) + + history_entry = { + "task_id": task.task_id, + "objective": task.objective, + "status": task.status.value, + "steps": len(task.plan), + "facts": task.facts_extracted[-10:], + "tools_used": list(tools_used)[:10], + "artifacts_count": len(task_artifacts), + "summary": "; ".join(step_summaries)[:300], + "review": (review_result.get("content", ""))[:200], + } + + # Keep max 20 task histories (trim oldest) + session.task_history.append(history_entry) + if len(session.task_history) > 20: + session.task_history = session.task_history[-20:] + + # Clean up old artifacts from Redis to free memory + # Keep only artifacts from the last 2 tasks + recent_task_ids = {t["task_id"] for t in session.task_history[-2:]} + for artifact in artifacts: + if artifact.task_id not in recent_task_ids: + # Remove old artifact from Redis hash + key = f"{self.memory._prefix}:session:{session.session_id}:artifacts" + await self.memory._r.hdel(key, artifact.artifact_id) + + logger.info( + "Compacted task %s into history (%d facts, %d tools, %d artifacts → summary)", + task.task_id, len(task.facts_extracted), len(tools_used), len(task_artifacts), + ) + def _create_agent(self, role: AgentRole) -> PlannerAgent | CoderAgent | CollectorAgent | ReviewerAgent: """Instantiate a subagent for the given role.""" profile = self._profiles[role]