Jelajahi Sumber

框架上下文:刷新受保护角色信息并避免周期结果累积

为通用 Runner 增加 protected_context_refresh,在压缩摘要中重新注入最新角色上下文和任务信息。将 get_current_context 标记为仅保留最近一次输出,并覆盖 Worker、Validator、repair continuation 的压缩哨兵测试,避免旧 Bootstrap 在长会话中覆盖新 Attempt。
SamLee 11 jam lalu
induk
melakukan
8f3fb55c3b

+ 13 - 1
agent/agent/core/runner.py

@@ -200,6 +200,7 @@ class RunConfig:
 
     # --- 自定义元数据上下文 ---
     context: Dict[str, Any] = field(default_factory=dict)
+    protected_context_refresh: Optional[Dict[str, Any]] = None
 
     # Trusted framework-only marker used by LocalAgentExecutor.  Ordinary
     # presets keep their historical temperature/max-iteration precedence.
@@ -3790,7 +3791,18 @@ class AgentRunner:
                 return ""
             return f"## Current Plan\n\n{goal_tree.to_prompt(include_summary=True)}"
         trace = await self.trace_store.get_trace(trace_id) if self.trace_store else None
-        return await self._build_task_context(trace)
+        task_context = await self._build_task_context(trace)
+        if not config.protected_context_refresh:
+            return task_context
+        protected = json.dumps(
+            config.protected_context_refresh,
+            ensure_ascii=False,
+            sort_keys=True,
+            separators=(",", ":"),
+            default=str,
+        )
+        parts = [item for item in (task_context, f"## Protected Role Context\n\n{protected}") if item]
+        return "\n\n".join(parts)
 
     # ===== 辅助方法 =====
 

+ 5 - 0
agent/agent/orchestration/executor.py

@@ -162,6 +162,11 @@ class LocalAgentExecutor:
             enable_research_flow=False,
             knowledge=_disabled_knowledge(),
             context=protected,
+            protected_context_refresh={
+                "role_context": context.get("role_context", {}),
+                "task_spec": context.get("task_spec", {}),
+                "attempt_id": context.get("attempt_id"),
+            },
             name=name,
         )
         try:

+ 1 - 0
agent/agent/tools/builtin/context.py

@@ -102,4 +102,5 @@ async def get_current_context(
         title="📋 当前执行上下文",
         output=context_content,
         long_term_memory="已刷新执行上下文",
+        include_output_only_once=True,
     )

+ 37 - 0
agent/tests/test_context_budget.py

@@ -4,6 +4,7 @@ import pytest
 
 from agent.core.prompts import build_summary_header
 from agent.core.runner import AgentRunner, RunConfig
+from agent.orchestration import CompletionPolicy
 from agent.trace.compaction import (
     CompressionConfig,
     calibrate_prompt_estimate,
@@ -188,6 +189,42 @@ async def test_oversized_minimum_context_fails_before_model_request(tmp_path):
     assert "context_budget_exceeded" in [event["event"] for event in events]
 
 
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sentinel", ["worker-latest", "validator-latest", "repair-latest"])
+async def test_compression_refreshes_latest_protected_role_context(tmp_path, sentinel):
+    store = FileSystemTraceStore(str(tmp_path))
+    trace = Trace(
+        trace_id=f"trace-{sentinel}",
+        mode="agent",
+        agent_role="worker",
+        context={"root_trace_id": "root"},
+    )
+    await store.create_trace(trace)
+
+    class Coordinator:
+        async def task_context(self, root_trace_id):
+            assert root_trace_id == "root"
+            return '{"state_revision":"ledger:latest"}'
+
+    runner = AgentRunner(trace_store=store, task_coordinator=Coordinator())
+    config = RunConfig(
+        completion_policy=CompletionPolicy.EXPLICIT_VALIDATION,
+        protected_context_refresh={"role_context": {"sentinel": sentinel}},
+    )
+    context = await runner._current_execution_context(trace.trace_id, None, config)
+
+    assert "ledger:latest" in context
+    assert sentinel in context
+    rebuilt = runner._rebuild_history_after_compression(
+        [
+            {"role": "system", "content": "policy"},
+            {"role": "user", "content": "old attempt bootstrap"},
+        ],
+        {"role": "user", "content": f"summary\n\n{context}"},
+    )
+    assert sentinel in rebuilt[-1]["content"]
+
+
 @pytest.mark.parametrize(
     "kwargs",
     [

+ 2 - 0
agent/tests/test_mode_isolation.py

@@ -122,6 +122,8 @@ async def test_explicit_current_context_uses_task_ledger_read_model(tmp_path):
     assert "Current Task Plan" in result.output
     assert "deliver result" in result.output
     assert "GoalTree" not in result.output
+    assert result.include_output_only_once is True
+    assert "deliver result" not in result.to_llm_message(first_time=False)
     compression_context = await runner._current_execution_context(
         "planner",
         None,