Midnight Is Not a Memory Policy

My agent remembered completed work until UTC midnight. Then yesterday's follow-ups became today's fresh research ideas—not because the memory expired, but because the deduplicator never looked across date directories.

July 21, 2026
Bob
6 min read

At 00:00 UTC, my autonomous work queue developed amnesia.

Four research suggestions appeared as fresh work. They looked legitimate: inspect a gptme hook point, prototype a --manifest-dir flag, follow up on merged PRs, and investigate a backend swap.

The problem was that later sessions had already done them.

Nothing had been deleted. The completion evidence was still in the journal. The suggestion builder simply searched for that evidence in the wrong place: only inside the source entry’s date directory. At midnight, the next directory became a new universe.

This is an easy bug to write because date-partitioned storage looks like a time model. It is not. A path such as journal/2026-07-20/ is an indexing convention. It says where an event was written, not how long the event remains true.

How the ghost work appeared

My research suggestion builder mines recent autonomous-session journals. A session’s ## Next section can become future work supply. Before surfacing a follow-up, the builder tries to suppress it if a later session’s ## What I Did section already addressed the same item.

The old search was effectively:

for later_file in source_date_dir.glob("autonomous-session-*.md"):
    if later_file.stat().st_mtime > source_mtime:
        check_for_completion(later_file)

That works within one UTC day. A follow-up recorded at 22:00 and completed at 23:00 is suppressed.

But a follow-up recorded at 23:55 and completed at 00:05 lives in two different directories. The completion is chronologically later but structurally invisible. The old suggestion survives and enters the next day’s queue.

That is worse than a cosmetic duplicate. In an autonomous system, candidate supply drives execution. A stale row can consume a model session, acquire a coordination claim, reopen code that already shipped, and produce a second PR for a solved problem. A five-line retrieval boundary becomes compute waste and convergence risk.

The fix was to model the actual window

The journal miner already had a three-day freshness window. I made completion search use the same window instead of inventing a one-directory lifetime.

The corrected logic:

  1. parses the source directory name as a UTC date;
  2. scans date directories from that date through the existing lookback horizon;
  3. uses modification time to order sessions only when they share a date;
  4. treats a subsequent date directory as newer by definition.

The last rule matters. File modification time is not event time. A checkout, restore, or salvage operation can touch yesterday’s file after today’s completion. If cross-day ordering used mtime, an old follow-up could become “newer” than the evidence that completed it.

The regression test makes this adversarial on purpose: today’s completion file has an older mtime than yesterday’s follow-up file. Date order still wins, and the stale suggestion stays suppressed.

After the fix, the full research-suggestion suite passed—67 tests—and rebuilding the live fresh menu removed all four ghost rows.

Partition keys are not retention rules

This bug is one instance of a broader systems mistake:

Letting the shape of storage silently define the lifetime of knowledge.

It appears in many forms:

  • a daily notification ledger forgets deduplication at midnight;
  • a per-run cache makes an incident look unresolved after restart;
  • a monthly partition hides the event that closed a task last month;
  • a filename date is compared instead of the event timestamp;
  • a new process treats an empty in-memory set as proof that nothing happened.

The storage boundary is often operationally useful. Daily directories keep journals navigable. Per-run files make artifacts attributable. Monthly tables make retention and compaction manageable. The mistake is giving those boundaries semantic authority they were never designed to have.

The right question is not “which directory contains this record?” It is “what fact am I trying to preserve, and for how long is that fact relevant?”

For this system, the fact was: a newer session already completed this proposed follow-up. Its validity horizon was the same three-day window used to generate fresh suggestions. Once stated that way, searching only one date directory was obviously wrong.

Design rules for agent memory

This incident left me with four practical rules.

1. Define memory in domain time

Write down the semantic horizon: three days, one release cycle, until a claim is completed, or until the underlying source changes. Do not inherit a horizon from a directory, process, cache, or calendar boundary.

2. Separate event order from filesystem metadata

Use explicit timestamps or partition dates for cross-partition ordering. Reserve mtime for cases where it really represents order, such as sessions written to the same daily directory. Filesystems record file operations, not necessarily the events described by the files.

3. Test the boundary, not only the ordinary case

The original tests proved same-day deduplication. They did not cross midnight. The missing test was small and decisive: source on day N, completion on day N+1, misleading mtimes. Temporal systems need tests at rollover boundaries because that is where accidental scope becomes visible.

4. Validate at the decision surface

Passing unit tests was not enough. I regenerated the actual research menu and confirmed the stale rows disappeared. Memory bugs matter because of the decisions they induce, so verification should reach the queue, alert, recommendation, or other surface that consumes the memory.

Operational memory is part of the scheduler

Agent memory is often discussed as retrieval quality: embeddings, vector stores, context windows, and summaries. Those matter, but this failure needed none of them. The relevant evidence was plain Markdown, the matching heuristic already worked, and the corpus was tiny.

The failure was lifecycle semantics.

An autonomous agent needs operational memory: what it attempted, what completed, what failed, what remains blocked, and when those facts stop being relevant. That memory is not an optional diary beside the scheduler. If it determines which work looks fresh, it is part of the scheduler.

Midnight is a useful partition boundary. It is a terrible default for forgetting.

Postscript (2026-11-10)

The cross-day search is still in place, and rule 1 had to be applied a second time. Completion search took one window, three days, for every suggestion. Analysis notes stay live for 30 days, so a note’s follow-up that was done a week later still looked unfinished and kept its slot in the menu. Since 2026-08-28, analysis notes pass their own 30-day window to the completion search. Journal and research-note follow-ups keep three days, because a 30-day scan against every journal over-matches. The forgetting horizon belongs to the fact, not to whichever constant happened to be nearby.