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>
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 testscrates/memoryd/tests/tiers.rsandcrates/bxctl/tests/cli_memory_tiers.rs, and the starterconfig/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
-
MemoryCommandisrename_all = "snake_case". JSON is"tiers". Add the variant afterSync, with a doc comment.admin.rsmatch 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.
hitsandlastfor identity are always0and-. For a note,hitsis how manyrecalls/*.jsonllines contain a hit withkind == "noted",session == note.session,record ==that note's index innotes::parse(from 0).lastis the maximumtimestring among those lines, or-. Class ispublic/private/secret(notes::class_name). Trust isuntrustedwhenKind::Noted { untrusted: true }, elsetrusted. Moved/dropped notes: still list them asnoteusing the same line, classprivate,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/epochon the line. Usedeny_unknown_fieldsoff soepochandquerydo not fail.) -
Identity.
store.entries(MemoryFile::Memory)thenUser. Empty file: no identity lines for that file. -
listreturnsResult<String, String>. Trailing newline on the last block is fine;bxctladds another when printingMemoryAdminReply.text(writeln!). The given bxctl test includes a trailing newline in the reply text and expects it pluswriteln!'s newline. -
adminarm.MemoryCommand::Tiers => tiers::list(&self.store, now)—nowis unused bylisttoday; keep the signaturelist(store, now)so the given tests compile. -
bxctl.["tiers"] => Ask::Admin(MemoryCommand::Tiers, String::new()).["tiers", _]is usage. USAGE: add| tierson thebxctl memoryline. -
rustfmt
modorder inmemoryd/src/lib.rs. -
The items the edits use. This table is complete.
Item What it does Store::entriesidentity notes::parse/class_namenotes Store::path("recalls")the jsonl directory MemoryFile::namememory.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:tiersdoes not compile (E0432unresolvedmemoryd::tiers);cli_memory_tiersdoes not compile (E0599no variantTiers). After M1 alone,admin.rsfails (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.rswith a reallist,pub mod tiers;, A1, B1, U1.cargo check -p bxcore -p memoryd -p bxctlprints no error and no warning.wc -l crates/memoryd/src/tiers.rsprints a number ≤ 500. -
4. The tests.
cargo test -p memoryd --test tiers(4) andcargo test -p bxctl --test cli_memory_tiers --test cli_memory --test cli_memory_sync(2 new): every test passes. -
5. Check.
git diff --statlists the files in "Every place that changes" except the new tests and starter (those are untracked untilgit add). -
6. Run the gate.
cargo fmt --all, thenmake gate, timeout 600000 ms. Last linegate: ok. Test count 1574 (1568 before, plus 6). -
7. Log and commit. Task cell
M5d2/10-memoryd-tiers. Stage withgit add crates/bxcore crates/memoryd crates/bxctl config/jobs docs/implementer-log.md Cargo.lock, then commit asAGENTS.mdsays.
Done when
make gateprintsgate: okwith 1574 tests.git diff HEAD~1 --statlists 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 checkwarns; a test fails; the gate count is not 1574; you editcli_memory.rs; identity entries show a non-zero hit count; untrusted notes are omitted; you add a cron crate; you special-case the nametierinloopd.
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.