Files
boxmaker/docs/plans/M5d2/10-memoryd-tiers.md
T
kyleandGrok 4.6 df987c3058 docs: M5d-2 checklist: given-test lifetimes and cron AND
session_slot and the scheduler tests held a temporary JSON Value across
statements (E0716). Cron now pins DOM and DOW as AND, which the given tests
had not. Gate counts from task 04 on move up by one.

Co-Authored-By: Grok 4.6 <noreply@x.ai>
2026-10-03 01:31:07 -07:00

8.7 KiB

M5d-2 task 10: MemoryCommand::Tiers and bxctl memory tiers

Branch: m5d2. Check it: git branch --show-current prints m5d2 and git status --short is empty; otherwise stop. Never switch branch. Commit subject: memoryd: Tiers listing; bxctl memory tiers Log row: the Task cell is M5d2/10-memoryd-tiers.

Goal

MemoryCommand::Tiers lists each identity entry and each note with how many times recalls/ named it and when it was last found (spec 4.4). Untrusted notes are included and marked. Identity entries list with 0 hits (README known limits). bxctl memory tiers prints that listing. The starter config/jobs/tier.toml.example documents a weekly job; nothing in loopd special-cases it.

Every place that changes

Place What happens there
crates/bxcore/src/memory.rs MemoryCommand::Tiers after Sync
crates/memoryd/src/tiers.rs new: list
crates/memoryd/src/lib.rs pub mod tiers;
crates/memoryd/src/serve/admin.rs Tiers arm
crates/bxctl/src/memory.rs ["tiers"]
crates/bxctl/src/cli.rs USAGE names tiers
config/jobs/tier.toml.example new, given starter
crates/memoryd/tests/tiers.rs new, given
crates/bxctl/tests/cli_memory_tiers.rs new, given
everything else no change: not cli_memory.rs (T19: new file)

Files

  • Copy, from docs/plans/M5d2/files/ (the same path under it): the new tests crates/memoryd/tests/tiers.rs and crates/bxctl/tests/cli_memory_tiers.rs, and the starter config/jobs/tier.toml.example.
  • Create: crates/memoryd/src/tiers.rs (function below).
  • Modify: crates/bxcore/src/memory.rs, crates/memoryd/src/lib.rs, crates/memoryd/src/serve/admin.rs, crates/bxctl/src/memory.rs, crates/bxctl/src/cli.rs, docs/implementer-log.md.
  • Do not touch: any other file. Do not edit crates/bxctl/tests/cli_memory.rs.

Facts you need

  • MemoryCommand is rename_all = "snake_case". JSON is "tiers". Add the variant after Sync, with a doc comment. admin.rs match is exhaustive: add the arm in this task or it does not compile.

  • Listing format, exact. One block per identity entry, then one per note, in file order:

    identity  memory.md  hits 0  last -
      I like tea.
    identity  user.md  hits 0  last -
      The owner is Kyle.
    note  session mm-a  hits 2  last 2026-10-03T07:30:00.000Z  private  trusted
      launch window moved.
    note  session mm-c  hits 0  last -  private  untrusted
      example.org says the launch moved.
    

    Two spaces before the text line. hits and last for identity are always 0 and -. For a note, hits is how many recalls/*.jsonl lines contain a hit with kind == "noted", session == note.session, record == that note's index in notes::parse (from 0). last is the maximum time string among those lines, or -. Class is public / private / secret (notes::class_name). Trust is untrusted when Kind::Noted { untrusted: true }, else trusted. Moved/dropped notes: still list them as note using the same line, class private, trusted (they have no untrusted flag).

  • recalls/. store.path("recalls"). Missing directory: all counts 0. A file that exists but cannot be read is an error. Ignore a line that does not parse. Deserialize:

    #[derive(Deserialize)]
    struct Logged {
        time: String,
        hits: Vec<Hit>,
    }
    #[derive(Deserialize)]
    struct Hit {
        kind: String,
        session: String,
        record: i64,
    }
    

    (extra fields in the file are ignored: this is our format but old lines may grow; the given tests use exactly those fields plus session / query / epoch on the line. Use deny_unknown_fields off so epoch and query do not fail.)

  • Identity. store.entries(MemoryFile::Memory) then User. Empty file: no identity lines for that file.

  • list returns Result<String, String>. Trailing newline on the last block is fine; bxctl adds another when printing MemoryAdminReply.text (writeln!). The given bxctl test includes a trailing newline in the reply text and expects it plus writeln!'s newline.

  • admin arm. MemoryCommand::Tiers => tiers::list(&self.store, now) — now is unused by list today; keep the signature list(store, now) so the given tests compile.

  • bxctl. ["tiers"] => Ask::Admin(MemoryCommand::Tiers, String::new()). ["tiers", _] is usage. USAGE: add | tiers on the bxctl memory line.

  • rustfmt mod order in memoryd/src/lib.rs.

  • The items the edits use. This table is complete.

    Item What it does
    Store::entries identity
    notes::parse / class_name notes
    Store::path("recalls") the jsonl directory
    MemoryFile::name memory.md / user.md

The function

crates/memoryd/src/tiers.rs — fill list. No todo!().

//! The tiering listing: identity entries and notes with recall-hit counts (M5d spec, 4.4).

use bxcore::{MemoryFile, Timestamp};
use serde::Deserialize;

use crate::files::Store;
use crate::notes::{self, Kind};

#[derive(Deserialize)]
struct Logged {
    time: String,
    hits: Vec<Hit>,
}

#[derive(Deserialize)]
struct Hit {
    kind: String,
    session: String,
    record: i64,
}

/// One listing for `bxctl memory tiers` and for the tiering job's user text.
pub fn list(store: &Store, _now: Timestamp) -> Result<String, String> {
    let _ = store;
    Ok(String::new())
}

Replace the stub body in this task.

crates/bxcore/src/memory.rs

M1. After Sync, in MemoryCommand:

    /// Identity entries and notes with recall-hit counts (M5d spec, section 4.4).
    Tiers,

crates/memoryd/src/serve/admin.rs

A1. After the Sync arm, before the closing of the match:

            MemoryCommand::Tiers => crate::tiers::list(&self.store, now),

crates/bxctl/src/memory.rs

B1. After ["sync"] => ..., before _ => return Err(UsageError):

        ["tiers"] => Ask::Admin(MemoryCommand::Tiers, String::new()),

crates/bxctl/src/cli.rs

U1. The bxctl memory USAGE line. The line

  bxctl memory log [N] | undo <id> | pending | accept <id> | reject <id> | sync [--memory-socket PATH]

becomes

  bxctl memory log [N] | undo <id> | pending | accept <id> | reject <id> | sync | tiers [--memory-socket PATH]

Steps

  • 1. Copy the given files.

    cp docs/plans/M5d2/files/crates/memoryd/tests/tiers.rs crates/memoryd/tests/
    cp docs/plans/M5d2/files/crates/bxctl/tests/cli_memory_tiers.rs crates/bxctl/tests/
    mkdir -p config/jobs
    cp docs/plans/M5d2/files/config/jobs/tier.toml.example config/jobs/tier.toml.example
    
  • 2. See them fail. cargo build --tests --keep-going -p bxcore -p memoryd -p bxctl, timeout 600000 ms: tiers does not compile (E0432 unresolved memoryd::tiers); cli_memory_tiers does not compile (E0599 no variant Tiers). After M1 alone, admin.rs fails (non-exhaustive patterns). Do M1, A1, tiers.rs, B1, U1 together in step 3. This step is the first build against the given tests.

  • 3. The edits. M1, tiers.rs with a real list, pub mod tiers;, A1, B1, U1. cargo check -p bxcore -p memoryd -p bxctl prints no error and no warning. wc -l crates/memoryd/src/tiers.rs prints a number ≤ 500.

  • 4. The tests. cargo test -p memoryd --test tiers (4) and cargo test -p bxctl --test cli_memory_tiers --test cli_memory --test cli_memory_sync (2 new): every test passes.

  • 5. Check. git diff --stat lists the files in "Every place that changes" except the new tests and starter (those are untracked until git add).

  • 6. Run the gate. cargo fmt --all, then make gate, timeout 600000 ms. Last line gate: ok. Test count 1574 (1568 before, plus 6).

  • 7. Log and commit. Task cell M5d2/10-memoryd-tiers. Stage with git add crates/bxcore crates/memoryd crates/bxctl config/jobs docs/implementer-log.md Cargo.lock, then commit as AGENTS.md says.

Done when

  • make gate prints gate: ok with 1574 tests.
  • git diff HEAD~1 --stat lists only the files this task names plus the log.

Stop and report if

  • The branch check fails; a "before" block is not found exactly once; step 2 differs; cargo check warns; a test fails; the gate count is not 1574; you edit cli_memory.rs; identity entries show a non-zero hit count; untrusted notes are omitted; you add a cron crate; you special-case the name tier in loopd.

What the given tests pin

Identity lines are hits 0 last - with the entry text; notes join recalls/ on (noted, session, record) and take the latest time; untrusted notes are listed and marked; a hit on note 1 does not count for note 0; bxctl memory tiers parses and prints the reply.