Skip to content

fix(nap): archiveDecisions() silently fails — undated entries immune + no count-based fallback #626

Closed

Description

@diberry

Bug Report: archiveDecisions() silently fails on real-world content

Summary

Two logic bugs in packages/squad-cli/src/cli/core/nap.ts archiveDecisions() cause it to silently return null on real-world decisions.md files, allowing unbounded growth. A user reported their decisions.md reached 145KB / 2,219 lines / ~35K tokens — every agent spawn was burning 35K tokens on stale decisions before doing any work.

Bug 1: Undated entries immune to archival

Location: archiveDecisions(), lines ~335-343

daysAgoFromLine() returns null for entries without YYYY-MM-DD in their ### heading. The split logic puts null daysAgo into recent (the keep bucket) unconditionally:

if (e.daysAgo !== null && e.daysAgo > DECISION_MAX_AGE_DAYS) {
  old.push(e);
} else {
  recent.push(e);  // ← undated entries end up here, never archived
}

Impact: Any ### entry without a date is immortal. Foundational directives written before the date convention was established (like ### Type safety — strict mode non-negotiable) can never be archived.

Note: These undated entries are often foundational directives that should be preserved. The fix needs to distinguish "undated but permanent" from "undated and stale." A lifecycle state field (Status: permanent) would solve this cleanly — but even without it, the count-based fallback (Bug 2 fix) handles the growth problem.

Bug 2: Age-only pruning fails for active projects

Location: archiveDecisions(), line ~345

if (old.length === 0) return null;  // ← 345KB file, returns null

If ALL entries are <30 days old (realistic for any active project generating 15 decisions/session), archival does nothing regardless of file size. There is no secondary threshold, no count-based fallback.

Impact: An active project will blow past 20KB within days, and squad nap will report "nothing to clean up" while the file is hundreds of KB.

Origin

Both bugs trace to commit aaefeb1 (Feb 28 2026), where the implementation and all 38 tests were written in a single pass. Tests used synthetic 2024-01-XX dated fixtures — the parser's assumption ("entries always have dates") was never challenged, and the all-recent early-exit was never reached.

Existing Work (fork PR)

A TDD fix has been implemented and reviewed on diberry/squad:

Review status (3 reviewers):

  • ✅ Flight (Lead): Approved — correctness, data safety, architecture all clean
  • ✅ FIDO (Quality): Approved — test coverage adequate, data-loss invariant verified
  • ❌ Procedures (Prompt Engineer): Rejected on one point — undated entries in this codebase are foundational directives that should NOT be archived blindly. Recommended: keep undated in recent until lifecycle states exist, rely on count-based fallback for size control.

Recommended Fix

Minimal (Bug 2 only — safest):

  • Add count-based fallback to archiveDecisions() — when all entries are recent AND file is over threshold, keep the most-recent N entries that fit under 20KB, archive the rest
  • Leave undated entries as-is (kept forever) until a Status: permanent convention is established

Full (both bugs):

  • Count-based fallback (as above)
  • Treat undated entries as archivable BUT add a permanent marker convention first
  • Update squad.agent.md:869 Scribe template to match nap.ts behavior

Related Issues (on diberry/squad)

# Title
#20 decisions.md grows unbounded (parent issue + 10-agent team review)
#21 Diagnostics framework — squad doctor, blame attribution
#22-37 Follow-up action items from team review and postmortem

Postmortem

Full postmortem available at: diberry#20 (comment)

Key lesson: "AI writes the tests it believes in, not the tests that break it." Adversarial test generation must be a deliberate separate step by a different agent.

Activity

  1. diberry commented on Mar 26, 2026

    @diberry
    ContributorAuthor

    cc @bradygaster — this was found while investigating decisions.md scaling for a Squad user. The fork PR at diberry#38 has a TDD fix with 5 new tests (43/43 pass), full postmortem, and 3-reviewer code review. The count-based fallback (Bug 2) is approved by all reviewers; the undated-entry handling (Bug 1) needs a design decision on whether undated = foundational (keep forever) or undated = stale (archive). Happy to discuss.

  2. bradygaster commented on Mar 26, 2026

    @bradygaster
    Collaborator

    good one!

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions