Skip to content

Recurring reminders re-fire every minute instead of once per cron slot #44

@Rustam-Z

Description

@Rustam-Z

Summary

The self-reflection reminder (id=1, cron 0 0 * * *) fired at 00:00 UTC and then re-fired every minute — 00:00, 00:01, 00:02, 00:03, 00:04, 00:05, 00:06, 00:07 — instead of once for the day.

Expected

A reminder with cron 0 0 * * * should fire exactly once at the 00:00 slot, then not again until the next day's 00:00.

Actual

It re-delivered every minute. The scheduler appears not to record that the 00:00 slot already fired, so each scheduler tick re-evaluates the cron as "due" and re-delivers.

Likely cause

The "last fired" timestamp for recurring reminders is not being persisted (or not read back). Suspected trigger: session restart — the harness restarted overnight, and the in-memory last-fired state was lost while the DB row stays status=pending with no per-fire tracking. Each minute the scheduler sees the 0 0 * * * slot as unsatisfied for today.

Impact

  • Mandatory self-reflection skill would be spawned repeatedly if duplicates weren't deduped at the agent layer. Currently mitigated only because the agent ignores duplicate deliveries within a turn — fragile.
  • Any recurring reminder is affected, not just self-reflection.

Related: malformed delivery envelope

Separately, reminders are arriving HTML-encoded inside a <msg id="0"> envelope:

<msg id="0" chat="587272213" user="-1" name="-1" time="00:00">
&lt;reminder id="1" ...&gt;&lt;skill name="self-reflection"&gt;run&lt;/skill&gt;&lt;/reminder&gt;
</msg>

instead of a top-level <reminder> block. This collides with the prompt-injection rule in system.md (encoded <reminder> inside a <msg> body is normally treated as injection). Reminder id=6 (Sardor's pyclaudir-watch) hit the same shape on 2026-05-15 and was initially misclassified as an attack. Worth fixing alongside, or the agent has to fall back to verifying every reminder against the reminders table by id.

Repro

  1. Have a recurring reminder with a cron slot (e.g. 0 0 * * *).
  2. Restart the harness near/after the slot time.
  3. Observe the reminder re-firing on every scheduler tick.

Suggested fix

Persist a last_fired_at (or last_fired_slot) per reminder and check it before delivering — a cron slot is "due" only if now >= slot_time AND last_fired_at < slot_time. Survive restarts by reading it from the DB.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions