From 5e9c474a3470a6562b1e02de7db0058ee251c98a Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Tue, 22 Sep 2026 17:02:43 +0800 Subject: [PATCH] fix(steward): use the window source_health rows in the manager guidance The evidence window publishes evidence_window.source_health - one typed row per declared source, including the local source, with freshness, the typed reason, a coverage_effect and a next_action - but the manager objective only named the remote_evidence rows, so those rows were delivered as data with no instruction to use them and a declared-but-unread source stayed an unexplained gap. Add the matching guidance sentence next to the remote_evidence line, keeping the coverage effect explicitly guidance rather than a machine-checked obligation, and pin the prompt text with a focused assertion. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- loopx/chat_manager.py | 1 + tests/test_chat_manager_context.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/loopx/chat_manager.py b/loopx/chat_manager.py index e68e4d9e0..74d907ce3 100644 --- a/loopx/chat_manager.py +++ b/loopx/chat_manager.py @@ -84,6 +84,7 @@ "remote_evidence declares each source's typed status and freshness: read or cached with its read time, unavailable with its typed reason_code, its reason, the last successful read and a coverage effect, not_configured for alias drift, or deferred_budget for a source outside this Turn's one-dial budget. " "When a source is unavailable, use its reason_code and reason to name the exact cause and the repair it needs in this answer, including when that repair is the owner's own action on this machine such as renewing an expired Kerberos ticket; an untyped unavailable or a cause copied from another source is a wrong answer. " "Use its rows as the remote evidence and answer the question with them; rows marked stale are last known rather than current, and an unread, stale or unavailable source is a named coverage gap, never evidence that a remote Goal made no progress. " + "evidence_window.source_health carries one typed row per declared source, including the local source, with freshness, the typed reason, a coverage_effect and a next_action: name an unread, stale or unavailable source by its reason and give its next_action as the repair. Read coverage_effect as guidance about what that gap can support, not a machine-checked obligation. " "When the Turn declares remote_read as on_demand_tool instead, the declared sources were not read for you: read the chosen source_id with loopx_manager_read before answering, and name any source you did not read as the exact gap. " "Read each delivery's recorded_details: checkpoint_reason and observed_reality describe recorded findings, while result_class and probe_kind describe the reported validation. " "Synthesize concrete results and counterevidence across receipts; do not replace them with counts, IDs, follow-up plans, or generic missing-evidence disclaimers. " diff --git a/tests/test_chat_manager_context.py b/tests/test_chat_manager_context.py index 963a4b133..f05392883 100644 --- a/tests/test_chat_manager_context.py +++ b/tests/test_chat_manager_context.py @@ -10,6 +10,7 @@ import loopx.chat_manager_context as context from loopx.chat_manager import ( MANAGER_AGENT_GOAL_ID, + MANAGER_AGENT_OBJECTIVE, MANAGER_CONTEXT_VERSION, manager_model_config, manager_workspace, @@ -298,6 +299,23 @@ def test_source_health_rows_type_a_window_that_read_nothing(): assert read[0]["next_action"] is None +def test_manager_prompt_uses_the_window_source_health_rows_as_guidance(): + """The window publishes source_health rows, so the prompt has to name them. + + The rows ship as data in every Turn; without a matching sentence the answer + only reads remote_evidence, and a source that was declared but contributed + nothing stays an unexplained gap. The wording also keeps the coverage effect + explicitly guidance rather than a machine-checked obligation. + """ + + assert "evidence_window.source_health carries one typed row per declared source" in ( + MANAGER_AGENT_OBJECTIVE + ) + assert "including the local source" in MANAGER_AGENT_OBJECTIVE + assert "next_action as the repair" in MANAGER_AGENT_OBJECTIVE + assert "not a machine-checked obligation" in MANAGER_AGENT_OBJECTIVE + + def test_remote_source_rows_are_typed_and_never_read_as_no_progress(): declared = [ {"source_id": "local", "source_host": "local", "status": "available"},