From 7fcc2279ccaba68494f10bed4dd0480f1972e47c Mon Sep 17 00:00:00 2001 From: Illia Polosukhin Date: Thu, 5 Feb 2026 20:02:33 -0800 Subject: [PATCH] Route HEARTBEAT writes to workspace DB and broadcast notifications - Add dedicated "heartbeat" target in memory_write tool so the LLM routes HEARTBEAT.md writes to the database instead of the filesystem - Update tool description to clarify it's database-backed storage - Broadcast heartbeat notifications to all channels when no explicit notify target is configured, instead of silently logging them Co-Authored-By: Claude Opus 4.6 --- src/agent/agent_loop.rs | 18 +++++++++++++----- src/tools/builtin/memory.rs | 25 ++++++++++++++++++++----- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/agent/agent_loop.rs b/src/agent/agent_loop.rs index bdcc43b6..72397b9a 100644 --- a/src/agent/agent_loop.rs +++ b/src/agent/agent_loop.rs @@ -212,11 +212,19 @@ impl Agent { } } _ => { - // No target configured, just log - tracing::info!( - "Heartbeat notification (no target configured): {}", - &response.content - ); + // No explicit target, broadcast to all channels + // for the default user so notifications actually + // reach someone instead of vanishing into logs. + let results = channels.broadcast_all("default", response).await; + for (ch, result) in results { + if let Err(e) = result { + tracing::warn!( + "Failed to broadcast heartbeat to {}: {}", + ch, + e + ); + } + } } } } diff --git a/src/tools/builtin/memory.rs b/src/tools/builtin/memory.rs index d50b4ee4..d77b3c59 100644 --- a/src/tools/builtin/memory.rs +++ b/src/tools/builtin/memory.rs @@ -133,10 +133,11 @@ impl Tool for MemoryWriteTool { } fn description(&self) -> &str { - "Write to persistent memory. Use for important facts, decisions, preferences, \ - or lessons learned that should be remembered across sessions. Use 'memory' target \ - for curated long-term facts, 'daily_log' for timestamped session notes, or \ - provide a custom path for arbitrary file creation." + "Write to persistent memory (database-backed, NOT the local filesystem). \ + Use for important facts, decisions, preferences, or lessons learned that should \ + be remembered across sessions. Targets: 'memory' for curated long-term facts, \ + 'daily_log' for timestamped session notes, 'heartbeat' for the periodic \ + checklist (HEARTBEAT.md), or provide a custom path for arbitrary file creation." } fn parameters_schema(&self) -> serde_json::Value { @@ -149,7 +150,7 @@ impl Tool for MemoryWriteTool { }, "target": { "type": "string", - "description": "Where to write: 'memory' for MEMORY.md, 'daily_log' for today's log, or a path like 'projects/alpha/notes.md'", + "description": "Where to write: 'memory' for MEMORY.md, 'daily_log' for today's log, 'heartbeat' for HEARTBEAT.md checklist, or a path like 'projects/alpha/notes.md'", "default": "daily_log" }, "append": { @@ -214,6 +215,20 @@ impl Tool for MemoryWriteTool { .map_err(|e| ToolError::ExecutionFailed(format!("Write failed: {}", e)))?; format!("daily/{}.md", chrono::Utc::now().format("%Y-%m-%d")) } + "heartbeat" => { + if append { + self.workspace + .append(paths::HEARTBEAT, content) + .await + .map_err(|e| ToolError::ExecutionFailed(format!("Write failed: {}", e)))?; + } else { + self.workspace + .write(paths::HEARTBEAT, content) + .await + .map_err(|e| ToolError::ExecutionFailed(format!("Write failed: {}", e)))?; + } + paths::HEARTBEAT.to_string() + } path => { if append { self.workspace