fix(heartbeat): prevent internal reasoning leaks and finalization fallback in delivery

Three failure modes addressed:

1. Model reflects HEARTBEAT.md instructions back as output instead of
   executing them ("HEARTBEAT.md has active tasks listed...")
2. Model narrates decision logic ("Best judgment call: stay quiet")
3. Model produces empty output for silence, runner treats it as failure,
   finalization retry generates "couldn't produce a final answer" which
   gets delivered to the user

Changes:
- Add _is_deliverable() pre-filter in HeartbeatService._tick() that catches
  finalization fallback messages and leaked reasoning patterns before they
  reach the evaluator
- Wrap Phase 2 task input with a delivery-awareness preamble telling the
  model its output goes directly to the user's messaging app
- Add meta-reasoning suppression criterion to evaluator template

No changes to agent/loop.py, runner.py, providers, or config schema.
This commit is contained in:
hussein1362
2026-04-27 18:14:13 +08:00
committed by Xubin Ren
parent 9dc99d1b34
commit e72c415473
4 changed files with 293 additions and 9 deletions
+9 -1
View File
@@ -792,6 +792,14 @@ def _run_gateway(
return "cli", "direct"
# Create heartbeat service
heartbeat_preamble = (
"[Your response will be delivered directly to the user's messaging app. "
"Output ONLY the final user-facing message. Never reference internal "
"files (HEARTBEAT.md, AWARENESS.md, etc.), your instructions, or your "
"decision process. If nothing needs reporting, respond with just "
"'All clear.' and nothing else.]\n\n"
)
async def on_heartbeat_execute(tasks: str) -> str:
"""Phase 2: execute heartbeat tasks through the full agent loop."""
channel, chat_id = _pick_heartbeat_target()
@@ -800,7 +808,7 @@ def _run_gateway(
pass
resp = await agent.process_direct(
tasks,
heartbeat_preamble + tasks,
session_key="heartbeat",
channel=channel,
chat_id=chat_id,