Skip to content

feat(heartbeat): trigger periodic memory maintenance#233

Open
mczabca-boop wants to merge 2 commits intoTinyAGI:mainfrom
mczabca-boop:upstream-main-v0.0.14-memory-test
Open

feat(heartbeat): trigger periodic memory maintenance#233
mczabca-boop wants to merge 2 commits intoTinyAGI:mainfrom
mczabca-boop:upstream-main-v0.0.14-memory-test

Conversation

@mczabca-boop
Copy link
Collaborator

Summary

  • trigger memory maintenance from the existing heartbeat flow
  • keep the maintenance prompt in a dedicated markdown file instead of inlining it in the script
  • record maintenance completion per agent with a workspace timestamp file

Changes

  • update lib/heartbeat-cron.sh to prepend a maintenance prompt when an agent has not run memory maintenance for 7 days
  • store the maintenance prompt in memory-maintenance-heartbeat.md
  • track pending maintenance by heartbeat message id and only write last-memory-maintenance after a matching heartbeat response is observed
  • skip re-triggering maintenance for an agent while a previous maintenance run is still pending

Testing

  • bash -n lib/heartbeat-cron.sh
  • manually forced memorytester maintenance by setting .tinyclaw/last-memory-maintenance to 0 and lowering heartbeat interval to 10s
  • confirmed the maintenance heartbeat prompt was delivered to memorytester
  • confirmed /home/cheng/tinyclaw-workspace/memorytester/.tinyclaw/last-memory-maintenance was updated only after a matching heartbeat response was observed
  • manually added two similar memory files under memory/maintenance-test/ and confirmed the maintenance run cleaned them up and kept the existing authoritative memory

Breaking Changes

  • None

@greptile-apps
Copy link

greptile-apps bot commented Mar 18, 2026

Greptile Summary

This PR integrates periodic memory maintenance into the existing heartbeat cron flow: when an agent hasn't run maintenance in 7 days, the contents of memory-maintenance-heartbeat.md are prepended to the heartbeat prompt; completion is confirmed by matching the sent messageId in subsequent API response polls, and recorded in a per-agent .tinyclaw/last-memory-maintenance timestamp file.

Key design decisions:

  • Pending state is tracked in four in-memory associative arrays keyed by messageId or agentId, making it lightweight and restart-safe (a restart naturally resets in-flight state)
  • A 24-hour TTL (PENDING_MAINTENANCE_TTL) prevents an agent from being permanently blocked if its heartbeat response falls outside the ?limit=20 response window
  • All three issues raised in prior review rounds have been addressed: unset calls now use double quotes so variables expand correctly, the TTL/stale-cleanup mechanism is in place, and the function is correctly named mark_memory_maintenance_completed

Remaining minor concerns:

  • mark_memory_maintenance_completed does not guard against an empty agent_dir argument; while the current code path always populates it, a defensive check would prevent a silent write to /.tinyclaw/ if the array ever resolves empty
  • mark_memory_maintenance_completed is called with $(date +%s) at the response-check site (line 264) instead of the already-computed $NOW, spawning a redundant subprocess for a timestamp used in a 7-day comparison

Confidence Score: 4/5

  • Safe to merge; all previously flagged critical bugs are fixed and the two remaining issues are minor defensive-coding improvements
  • The core logic is sound: correct double-quoted unset in clear_pending_maintenance, proper TTL expiry via cleanup_stale_pending_maintenance, and an accurate function name. The only new findings are a missing empty-string guard in mark_memory_maintenance_completed (could misfire only in an unexpected edge case) and a redundant $(date +%s) subprocess call — both P2 style issues with no impact on normal operation.
  • No files require special attention beyond the two minor style suggestions in lib/heartbeat-cron.sh

Important Files Changed

Filename Overview
lib/heartbeat-cron.sh Adds memory maintenance triggering to the heartbeat loop using four associative arrays for tracking pending state; addresses all prior review concerns (double-quoted unset, TTL cleanup, function naming); two minor style issues remain (missing guard in mark_memory_maintenance_completed, redundant $(date +%s) subprocess).
memory-maintenance-heartbeat.md New prompt file for memory maintenance; clear instructions, correctly ends with "Then continue with this heartbeat task:" so the heartbeat prompt appended by the script follows naturally after the two-newline separator.

Sequence Diagram

sequenceDiagram
    participant Cron as heartbeat-cron.sh
    participant FS as Agent Filesystem
    participant API as API Server
    participant Agent as Agent Process

    loop Every BASE_INTERVAL seconds
        Cron->>Cron: cleanup_stale_pending_maintenance(NOW)

        loop For each AGENT_ID
            Cron->>FS: read .tinyclaw/last-memory-maintenance
            FS-->>Cron: timestamp (or missing)
            alt maintenance overdue (≥7 days) AND not already pending
                Cron->>FS: read memory-maintenance-heartbeat.md
                FS-->>Cron: maintenance prompt
                Cron->>Cron: prepend prompt to PROMPT
            end
            Cron->>API: POST /api/message {agent, channel:heartbeat, message}
            API-->>Cron: {ok, messageId}
            Cron->>Cron: store PENDING_MAINTENANCE_DIR[messageId] = agentDir
        end

        Cron->>Cron: sleep 10s

        Cron->>API: GET /api/responses?limit=20
        API-->>Cron: [{channel, messageId, ...}, ...]

        loop For each pending messageId
            alt response with matching messageId found
                Cron->>FS: write .tinyclaw/last-memory-maintenance
                Cron->>Cron: clear_pending_maintenance(messageId, agentId)
            end
        end
    end
Loading

Last reviewed commit: "fix(heartbeat): clea..."

@mczabca-boop
Copy link
Collaborator Author

@greptile-apps review again

Comment on lines +77 to +82
mark_memory_maintenance_completed() {
local agent_dir="$1"
local now="$2"
mkdir -p "$agent_dir/.tinyclaw"
printf '%s\n' "$now" > "$agent_dir/.tinyclaw/last-memory-maintenance"
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing guard for empty agent_dir

mark_memory_maintenance_completed doesn't validate that agent_dir is non-empty before writing. If PENDING_MAINTENANCE_DIR[$MESSAGE_ID] somehow resolves to an empty string (e.g. an unexpected bash edge case or array misuse), mkdir -p "/.tinyclaw" and printf … > "/.tinyclaw/last-memory-maintenance" would execute — silently succeeding on systems where the process has root privileges, or failing with a cryptic permission error otherwise.

A one-line guard at the top of the function keeps this safe:

Suggested change
mark_memory_maintenance_completed() {
local agent_dir="$1"
local now="$2"
mkdir -p "$agent_dir/.tinyclaw"
printf '%s\n' "$now" > "$agent_dir/.tinyclaw/last-memory-maintenance"
}
mark_memory_maintenance_completed() {
local agent_dir="$1"
local now="$2"
if [ -z "$agent_dir" ]; then
log " ✗ mark_memory_maintenance_completed: agent_dir is empty, skipping"
return 1
fi
mkdir -p "$agent_dir/.tinyclaw"
printf '%s\n' "$now" > "$agent_dir/.tinyclaw/last-memory-maintenance"
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant