Skip to content

Commit 37c440e

Browse files
authored
fix(work): keep narration truthful across steer
Fence stale pre-steer narration and status evidence, preserve evidence strength, and keep mechanical intake or unstructured tool output from masquerading as semantic progress.
1 parent 1005562 commit 37c440e

13 files changed

Lines changed: 763 additions & 67 deletions

agent_host/adapters/codex_app_server.py

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,27 +1183,53 @@ def _map_notification(
11831183
)
11841184
name = self._tool_name(safe_item_type, item)
11851185
if method == "item/started":
1186-
return [
1187-
self._event(
1188-
active,
1189-
"tool.call",
1190-
{
1191-
"name": name,
1192-
"item_id": item_id,
1193-
"input": self._tool_input(safe_item_type, item),
1194-
},
1186+
tool_input = self._tool_input(safe_item_type, item)
1187+
call_payload: dict[str, Any] = {
1188+
"tool": name,
1189+
"name": name,
1190+
"item_id": item_id,
1191+
"input": tool_input,
1192+
}
1193+
if isinstance(tool_input, dict):
1194+
command = str(tool_input.get("command") or "").strip()
1195+
title = " ".join(str(tool_input.get("title") or "").split())[:240]
1196+
changes = tool_input.get("changes")
1197+
if command:
1198+
call_payload["command"] = command[:2000]
1199+
if title:
1200+
call_payload["title"] = title
1201+
if isinstance(changes, list):
1202+
call_payload["changes"] = changes
1203+
events = []
1204+
direction = self._reported_tool_direction(safe_item_type, tool_input)
1205+
if direction:
1206+
events.append(
1207+
self._event(
1208+
active,
1209+
"assistant.update",
1210+
{
1211+
"text": direction,
1212+
"source": "codex_native_tool_title",
1213+
"explicit": False,
1214+
"status": "reported_direction",
1215+
},
1216+
)
11951217
)
1196-
]
1218+
events.append(self._event(active, "tool.call", call_payload))
1219+
return events
11971220
success = self._item_succeeded(item)
11981221
if not success:
11991222
state.tool_failures += 1
12001223
result_payload: dict[str, Any] = {
1224+
"tool": name,
12011225
"name": name,
12021226
"item_id": item_id,
12031227
"success": success,
12041228
"status": str(item.get("status") or ""),
12051229
"output": self._tool_output(item),
12061230
}
1231+
if safe_item_type == "commandExecution":
1232+
result_payload["exit_code"] = item.get("exitCode")
12071233
if safe_item_type == "fileChange":
12081234
result_payload["changes"] = self._file_changes(item)
12091235
events = [
@@ -1505,6 +1531,16 @@ def _tool_input(item_type: str, item: dict[str, Any]) -> Any:
15051531
return {"changes": CodexAppServerAdapter._file_changes(item)}
15061532
return item.get("arguments") or item.get("query") or {}
15071533

1534+
@staticmethod
1535+
def _reported_tool_direction(item_type: str, tool_input: Any) -> str:
1536+
"""Return only the native human-facing title, never code or tool output."""
1537+
1538+
if item_type in {"commandExecution", "fileChange"} or not isinstance(
1539+
tool_input, dict
1540+
):
1541+
return ""
1542+
return " ".join(str(tool_input.get("title") or "").split())[:240]
1543+
15081544
@staticmethod
15091545
def _file_changes(item: dict[str, Any]) -> list[dict[str, str]]:
15101546
raw = item.get("changes") if isinstance(item.get("changes"), list) else []

server/handlers/work_activity_handler.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,6 @@ async def _on_provider_event(self, _method: str, params: dict[str, Any]) -> None
244244
phase="Intake",
245245
progress=10,
246246
force=True,
247-
# The Host knows which bounded goal it just dispatched even
248-
# before the Provider reports an implementation milestone.
249-
# Surface that one truthful direction so a long cold start is
250-
# not two minutes of silence; results remain receipt-owned.
251-
narration_keypoint="directional_progress",
252247
)
253248
return
254249

@@ -831,6 +826,7 @@ def _accept_semantic_fact(
831826
state["semantic_explicit"] = fact.explicit
832827
state["semantic_verified"] = fact.verified
833828
state["semantic_milestone"] = fact.milestone
829+
state["semantic_evidence"] = fact.evidence
834830
return True
835831

836832
@staticmethod
@@ -1495,8 +1491,23 @@ async def _emit_progress_canvas(
14951491
{
14961492
"narration_keypoint": narration_keypoint,
14971493
**(
1498-
{"semantic_milestone": str(state.get("semantic_milestone") or "")}
1499-
if semantic and state.get("semantic_milestone")
1494+
{
1495+
**(
1496+
{
1497+
"semantic_milestone": str(
1498+
state.get("semantic_milestone") or ""
1499+
)
1500+
}
1501+
if state.get("semantic_milestone")
1502+
else {}
1503+
),
1504+
"semantic_source": str(state.get("semantic_source") or ""),
1505+
"semantic_verified": state.get("semantic_verified") is True,
1506+
"semantic_evidence": str(
1507+
state.get("semantic_evidence") or "reported"
1508+
),
1509+
}
1510+
if semantic and not semantic_candidate
15001511
else {}
15011512
),
15021513
**(
@@ -2001,9 +2012,11 @@ def _progress_signals(
20012012
label="report" if semantic else "stream",
20022013
text=self._trim(text, 110),
20032014
detail=(
2004-
"provider update; not terminal"
2015+
"reported direction; not verified"
20052016
if semantic_candidate
2006-
else "semantic"
2017+
else "Host-observed semantic evidence"
2018+
if semantic and state.get("semantic_verified") is True
2019+
else "provider-reported semantic evidence; not verified"
20072020
if semantic
20082021
else "streaming"
20092022
),

0 commit comments

Comments
 (0)