Task 06 opened the whole implementer log (~360 kB) after reading supporting modules; OpenCode returned "Context size has been exceeded" and made no commit. AGENTS.md and tasks 06-10 now say to read the first 12 lines only. Co-Authored-By: Grok 4.6 <noreply@x.ai>
254 lines
9.7 KiB
Markdown
254 lines
9.7 KiB
Markdown
# 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` |
|
|
| `crates/bxctl/tests/cli_memory_sync.rs` | USAGE pin matches U1 (edit S1) |
|
|
| `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`, `crates/bxctl/tests/cli_memory_sync.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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```rust
|
|
#[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.
|
|
- **Counts map.** A nested `HashMap<(String, i64), (u64, String)>` trips clippy
|
|
`type_complexity` (`-D warnings`). Name a small `Tally { n: u64, last: String }` and
|
|
return `HashMap<(String, i64), Tally>`. Do not `#[allow]`.
|
|
- **`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!()`.
|
|
|
|
```rust
|
|
//! 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`:
|
|
|
|
```rust
|
|
/// 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`:
|
|
|
|
```rust
|
|
MemoryCommand::Tiers => crate::tiers::list(&self.store, now),
|
|
```
|
|
|
|
### `crates/bxctl/src/memory.rs`
|
|
|
|
**B1.** After `["sync"] => ...`, before `_ => return Err(UsageError)`:
|
|
|
|
```rust
|
|
["tiers"] => Ask::Admin(MemoryCommand::Tiers, String::new()),
|
|
```
|
|
|
|
### `crates/bxctl/src/cli.rs`
|
|
|
|
**U1.** The `bxctl memory` USAGE line. The line
|
|
|
|
```text
|
|
bxctl memory log [N] | undo <id> | pending | accept <id> | reject <id> | sync [--memory-socket PATH]
|
|
```
|
|
|
|
becomes
|
|
|
|
```text
|
|
bxctl memory log [N] | undo <id> | pending | accept <id> | reject <id> | sync | tiers [--memory-socket PATH]
|
|
```
|
|
|
|
### `crates/bxctl/tests/cli_memory_sync.rs`
|
|
|
|
**S1.** `sync_parses` pins the USAGE line from M5c. U1 changes it. The lines
|
|
|
|
```rust
|
|
USAGE.contains("| reject <id> | sync [--memory-socket PATH]\n"),
|
|
"{USAGE}"
|
|
```
|
|
|
|
become
|
|
|
|
```rust
|
|
USAGE.contains("| reject <id> | sync | tiers [--memory-socket PATH]\n"),
|
|
"{USAGE}"
|
|
```
|
|
|
|
Do not edit `cli_memory.rs`. This file is the M5c pin of the same USAGE string.
|
|
|
|
## Steps
|
|
|
|
- [ ] **1. Copy the given files.**
|
|
|
|
```sh
|
|
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, S1.
|
|
`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). After `gate: ok`, do not add up
|
|
`test result:` lines.
|
|
- [ ] **7. Log and commit.** Task cell `M5d2/10-memoryd-tiers`. Read only the first 12 lines of
|
|
`docs/implementer-log.md`; insert the row under the header. 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's last line is not `gate: ok`; you edit `cli_memory.rs`; you leave
|
|
the `cli_memory_sync` USAGE pin on the old string; 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.
|