fix(heartbeat): suppress progress messages and deliver agent response to user

This commit is contained in:
Re-bin
2026-02-23 13:45:09 +00:00
parent 2c09f23c02
commit 7671239902
2 changed files with 32 additions and 20 deletions

View File

@@ -372,13 +372,21 @@ def gateway(
session_key="heartbeat",
channel=channel,
chat_id=chat_id,
# suppress: heartbeat should not push progress to external channels
on_progress=_silent,
on_progress=_silent, # suppress: heartbeat should not push progress to external channels
)
async def on_heartbeat_notify(response: str) -> None:
"""Deliver a heartbeat response to the user's channel."""
from nanobot.bus.events import OutboundMessage
channel, chat_id = _pick_heartbeat_target()
if channel == "cli":
return # No external channel available to deliver to
await bus.publish_outbound(OutboundMessage(channel=channel, chat_id=chat_id, content=response))
heartbeat = HeartbeatService(
workspace=config.workspace_path,
on_heartbeat=on_heartbeat,
on_notify=on_heartbeat_notify,
interval_s=30 * 60, # 30 minutes
enabled=True
)

View File

@@ -9,14 +9,15 @@ from loguru import logger
# Default interval: 30 minutes
DEFAULT_HEARTBEAT_INTERVAL_S = 30 * 60
# The prompt sent to agent during heartbeat
HEARTBEAT_PROMPT = """Read HEARTBEAT.md in your workspace (if it exists).
Follow any instructions or tasks listed there.
If nothing needs attention, reply with just: HEARTBEAT_OK"""
# Token that indicates "nothing to do"
# Token the agent replies with when there is nothing to report
HEARTBEAT_OK_TOKEN = "HEARTBEAT_OK"
# The prompt sent to agent during heartbeat
HEARTBEAT_PROMPT = (
"Read HEARTBEAT.md in your workspace and follow any instructions listed there. "
f"If nothing needs attention, reply with exactly: {HEARTBEAT_OK_TOKEN}"
)
def _is_heartbeat_empty(content: str | None) -> bool:
"""Check if HEARTBEAT.md has no actionable content."""
@@ -38,20 +39,24 @@ def _is_heartbeat_empty(content: str | None) -> bool:
class HeartbeatService:
"""
Periodic heartbeat service that wakes the agent to check for tasks.
The agent reads HEARTBEAT.md from the workspace and executes any
tasks listed there. If nothing needs attention, it replies HEARTBEAT_OK.
The agent reads HEARTBEAT.md from the workspace and executes any tasks
listed there. If it has something to report, the response is forwarded
to the user via on_notify. If nothing needs attention, the agent replies
HEARTBEAT_OK and the response is silently dropped.
"""
def __init__(
self,
workspace: Path,
on_heartbeat: Callable[[str], Coroutine[Any, Any, str]] | None = None,
on_notify: Callable[[str], Coroutine[Any, Any, None]] | None = None,
interval_s: int = DEFAULT_HEARTBEAT_INTERVAL_S,
enabled: bool = True,
):
self.workspace = workspace
self.on_heartbeat = on_heartbeat
self.on_notify = on_notify
self.interval_s = interval_s
self.enabled = enabled
self._running = False
@@ -113,15 +118,14 @@ class HeartbeatService:
if self.on_heartbeat:
try:
response = await self.on_heartbeat(HEARTBEAT_PROMPT)
# Check if agent said "nothing to do"
if HEARTBEAT_OK_TOKEN.replace("_", "") in response.upper().replace("_", ""):
logger.info("Heartbeat: OK (no action needed)")
if HEARTBEAT_OK_TOKEN in response.upper():
logger.info("Heartbeat: OK (nothing to report)")
else:
logger.info("Heartbeat: completed task")
except Exception as e:
logger.error("Heartbeat execution failed: {}", e)
logger.info("Heartbeat: completed, delivering response")
if self.on_notify:
await self.on_notify(response)
except Exception:
logger.exception("Heartbeat execution failed")
async def trigger_now(self) -> str | None:
"""Manually trigger a heartbeat."""