Files
boxmaker/docs/plans/M5a/10-memoryd-serve.md
kyleandClaude Opus 5.5 de1032157b Plan M5a: local memory in 15 tasks
memoryd (files, the check, edits, journal, index, recall, serving),
the memory tools and built-in grants in brokerd, the memory block in
loopd, bxctl reindex and memory, and the device checks with memoryd.

Each task was built as a reference and gated. Each was replayed on its
own at its end state (854 to 936 tests) and mutation-checked. Each
skeleton compiles and fails its tests. Every todo comment was read
beside its signature.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 15:32:54 -07:00

80 lines
3.2 KiB
Markdown

# M5a task 10: serving
**Branch:** `m5a` (run `git switch m5a`; `git status --short` must be empty, otherwise stop)
**Commit subject:** `memoryd: serve, three sockets and one lock over the files and the index`
## Goal
`memoryd serve --config <path>` (M5a spec, section 7): at start it creates what is missing under
`memory/`, opens the index and catches it up, then listens on three sockets, one request per
connection, each answered by one frame:
| Socket | Peer | Takes |
|---|---|---|
| `run/loop-memory/memory.sock` | `loopd` | `memory_block`, `session_updated` |
| `run/broker-memory/memory.sock` | `brokerd` | `memory_call` (`recall` or `memory`) |
| `run/owner-memory/admin.sock` | `bxctl` | `reindex`, `memory_admin` |
A message on the wrong socket is refused (`BadMessage`). One lock covers the files and the index,
so writes never interleave.
## Files
- Copy (skeleton): `crates/memoryd/src/serve.rs`
- Copy (test): `crates/memoryd/tests/serve.rs`
- Replace: `crates/memoryd/src/main.rs` with `docs/plans/M5a/files/crates/memoryd/src/main.rs-task10`
(task 03 gave it whole, and nothing has changed it since; this version starts the server)
- Modify: `crates/memoryd/src/lib.rs` (`pub mod serve;` after `pub mod review;`),
`crates/memoryd/src/index.rs`, `docs/implementer-log.md`
## `index.rs`, exactly
Add this function just before the doc comment `/// Every session log under `sessions`: `(session,
epoch, path)`, sorted.`:
```rust
/// The logs of one session: `(epoch, path)`, sorted. A session with no directory has none.
pub fn session_logs(sessions: &Path, session: &str) -> Result<Vec<(i64, PathBuf)>, String> {
let dir = sessions.join(session);
let files = match std::fs::read_dir(&dir) {
Ok(files) => files,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(e) => return Err(format!("{}: {e}", dir.display())),
};
let mut found = Vec::new();
for file in files.filter_map(|f| f.ok()) {
let name = file.file_name().to_string_lossy().into_owned();
if let Some(Ok(epoch)) = name.strip_suffix(".jsonl").map(str::parse::<i64>) {
found.push((epoch, file.path()));
}
}
found.sort();
Ok(found)
}
```
## The skeleton
Written: `READ_WAIT`, `Log`, `State`, `Memoryd`, `Running`, `Which`, `error`, `bad`,
`Memoryd::lock`, and **`Memoryd::handle`**, the table of which socket takes which message: read
it first. Fill: `start`, `Running::run`, `connection`, `block`, `session_updated`, `call`,
`recall`, `memory`, `index_notes`, `reindex`, `admin`.
## Steps
- [ ] **1. Copy** the two files, replace `main.rs`; edit `lib.rs` and `index.rs`.
- [ ] **2. See it fail.** `cargo test -p memoryd --test serve`: 8 failed.
- [ ] **3. Fill** the functions. `cargo fmt --all`.
- [ ] **4. See it pass.** The same command, three times: 8 passed each time, in about a second.
- [ ] **5. Run the gate.** `make gate`: `gate: ok`, 8 more tests than before (about 920).
- [ ] **6. Log and commit.** `git add -A crates/memoryd docs/implementer-log.md Cargo.lock && git commit`
## Done when
- `make gate` prints `gate: ok`.
## Stop and report if
- A test fails after the functions follow their comments exactly. Do not edit a test.
- A test waits more than a few seconds, or passes only sometimes.