Files
boxmaker/docs/plans/M5a/10-memoryd-serve.md
T
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

3.2 KiB

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.:

/// 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.