You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The heartbeat is currently observable only via timer ticks (default 30 min) and logs. There's no way to fire one from a known channel context, and no way to fire Phase 1 (the skip/run decision) without paying for Phase 2 (execution). This makes iteration on HEARTBEAT.md slow and makes several open issues hard to reproduce or diagnose:
Heartbeat Issue #2797 The agent completes a task but never updates HEARTBEAT.md, so the same task re-fires every tick. Reporters can only validate a candidate fix by waiting for the next interval.
为什么 heartbeat 默认是和主会话隔离的 #1899 When a heartbeat fires, its delivery channel is picked heuristically by _pick_heartbeat_target. There's no way to scope a fire to "the channel I'm asking from."
Across these, the missing feature is the same: a way to fire a heartbeat on demand, optionally without executing it, optionally scoped to a specific channel.
Concrete use cases:
Iterating on HEARTBEAT.md phrasing. Edit the file, fire a Phase-1-only check, see the skip/run decision and the extracted task summary. Iterate without paying for Phase 2 and without delivering noise to a real channel.
Per-channel debugging. Trigger from Telegram chat A and have the response delivered to A: useful when debugging delivery issues on a specific channel.
End-to-end test harnesses. Tests (and the agent itself, when asked) can drive the full heartbeat flow without monkey-patching the timer.
Proposed Solution
Expose an on-demand trigger with two actions, sharing the same underlying mechanics as a timer tick:
dry_run: Phase 1 only: read HEARTBEAT.md, ask the LLM, return the decision and extracted tasks. No execution.
run: Phase 1 + Phase 2: same path as a timer tick.
A shared lock with the timer so manual and timer fires can't collide.
Expose this via either (or both):
Agent-callable tool. Reachable from any channel, so users can debug from inside a conversation. The calling session's channel/chat is used as the delivery target for run.
CLI subcommand. Something like nanobot heartbeat trigger [--dry-run]. Fits a "developer tool" framing and stays out of the tool list the LLM sees on every turn.
Open questions:
Interface: tool, CLI, or both? A tool is reachable from any channel but adds to what the LLM sees on every turn (and risks the LLM firing it unprompted). A CLI is cleaner for a debugging-only feature but doesn't help users debugging from inside a chat.
Per-channel routing on timer ticks. Should all heartbeats remember an origin (e.g. the channel that last edited HEARTBEAT.md), or only manual triggers? The asymmetric option is simpler but means manual fires and timer fires can deliver to different chats from the same file.
Concurrency policy. When a manual trigger arrives mid-tick, should it refuse, queue, or coalesce? Refuse is simplest. Queueing risks runaway under repeated manual fires. Coalescing is the hardest to explain.
Alternatives Considered
Lower the timer interval during debugging. Doesn't help: still no Phase-1-only mode, still no per-channel scoping, and noisy for real users.
Monkey-patching the timer in tests. Works for tests but not for live debugging from a chat, and doesn't address the channel-routing case (为什么 heartbeat 默认是和主会话隔离的 #1899).
Particularly interested in input from anyone who has hit #2797, #1310, or #2406: would on-demand triggering (and especially Phase-1-only dry_run) have meaningfully shortened your debugging cycle?
Problem / Motivation
The heartbeat is currently observable only via timer ticks (default 30 min) and logs. There's no way to fire one from a known channel context, and no way to fire Phase 1 (the skip/run decision) without paying for Phase 2 (execution). This makes iteration on
HEARTBEAT.mdslow and makes several open issues hard to reproduce or diagnose:HEARTBEAT.md, so the same task re-fires every tick. Reporters can only validate a candidate fix by waiting for the next interval.tasksstring passed to Phase 2 is too brief, but there's no way to inspect the summary the LLM actually produces without running Phase 2._pick_heartbeat_target. There's no way to scope a fire to "the channel I'm asking from."Across these, the missing feature is the same: a way to fire a heartbeat on demand, optionally without executing it, optionally scoped to a specific channel.
Concrete use cases:
HEARTBEAT.mdphrasing. Edit the file, fire a Phase-1-only check, see the skip/run decision and the extracted task summary. Iterate without paying for Phase 2 and without delivering noise to a real channel.HEARTBEAT.mdexamples.Proposed Solution
Expose an on-demand trigger with two actions, sharing the same underlying mechanics as a timer tick:
dry_run: Phase 1 only: readHEARTBEAT.md, ask the LLM, return the decision and extracted tasks. No execution.run: Phase 1 + Phase 2: same path as a timer tick.Expose this via either (or both):
run.nanobot heartbeat trigger [--dry-run]. Fits a "developer tool" framing and stays out of the tool list the LLM sees on every turn.Open questions:
dry_run. Ship Phase-1-only mode, or justrun? Phase-1-only is the cheaper iteration loop and the more useful developer tool for [Feature] Skip heartbeat LLM call when HEARTBEAT.md has no active tasks #2406-style work, but it's a second action to document and learn.HEARTBEAT.md), or only manual triggers? The asymmetric option is simpler but means manual fires and timer fires can deliver to different chats from the same file.Alternatives Considered
Related Component
Agent / Prompts
Additional Context
Linked issues that motivate this: #2797, #1310, #2406, #1899.
Particularly interested in input from anyone who has hit #2797, #1310, or #2406: would on-demand triggering (and especially Phase-1-only
dry_run) have meaningfully shortened your debugging cycle?