# M5c-2 task 03: `brokerd` waits 170 s for `ask_memory` **Branch:** `m5c2`. Check it: `git branch --show-current` prints `m5c2` and `git status --short` is empty; otherwise stop. Never switch branch. **Commit subject:** `brokerd: a wait of its own for ask_memory, 170 s` **Log row:** the Task cell is `M5c2/03-brokerd-ask-memory-wait`. ## Goal `brokerd` hands each decided memory call to `memoryd` through a `MemoryPort` (`crates/brokerd/src/memory_port.rs`), and waits for the answer: today 30 s (`MEMORY_WAIT`), set as the socket's read and write timeouts. Task 02 made `ask_memory` a memory tool. Its answer can take much longer: `memoryd` asks Honcho's chat about each named workspace, both at once, with 150 s each (M5c spec, section 9). So `ask_memory` gets a wait of its own: - `ASK_MEMORY_WAIT` = 170 s, a constant, not configuration. `MEMORY_WAIT` stays 30 s, for `recall` and `memory`. - The port chooses the wait by the call's tool (`wait_for`), and sets it as the read and write timeouts of that call's connection only. - The tests need short waits, so the wait for `ask_memory` can be set on a port (`with_ask_wait`), as `new` already takes the wait for the other two. `MemoryPort::new` keeps its signature, and `brokerd`'s `main.rs` does not change: `new` gives every port `ASK_MEMORY_WAIT`. `loopd` waits 120 s for `brokerd` today, for any tool; task 04 gives `ask_memory` 180 s there. ## Every place that changes This is the complete list. | Place | What happens there | |---|---| | `crates/brokerd/src/memory_port.rs` | the module's doc comment; a `use` line; `ASK_MEMORY_WAIT`; `MemoryPort`'s new field `ask_wait`; `new`, `with_ask_wait`, `wait_for`; `ask` uses `wait_for` (edits W1 to W5) | | `crates/brokerd/tests/memory_wait.rs` | new, given | | everything else | **no change**: not `main.rs`, not `args.rs`, not the other tests, no `Cargo.toml` | ## Files - Copy, from `docs/plans/M5c2/files/` (the same path under it): the new test `crates/brokerd/tests/memory_wait.rs`. - Modify: `crates/brokerd/src/memory_port.rs`, `docs/implementer-log.md`. - Do not touch: any other file. ## Facts you need - **Struct update syntax.** `MemoryPort { ask_wait: wait, ..self }` builds a new `MemoryPort` that takes `ask_wait` from `wait` and every other field from `self` (moved: `with_ask_wait` takes `self` by value). It is what edit W3 writes. - **`ToolName`** is `crate::args::ToolName`, `Copy`, with the variant `AskMemory` since task 02. `wait_for` matches it with a `_` arm for every other tool: only `ask_memory` has its own wait. - **The given test** `memory_wait.rs` (3 tests): - the two constants; which tool gets which wait, with `new`'s own wait, with the default for `ask_memory`, and with `with_ask_wait`; - a fake `memoryd` that answers after 1.5 s: with a 300 ms wait for `recall` and `memory` and 20 s for `ask_memory`, the `ask_memory` call gets the answer and the other two get the `UNREACHABLE` failure; and the other way round for `recall` and `ask_memory`. A failed answer comes back within the short wait, and a good one as soon as it is written, so the file takes about 3 s. - The items the edits use. This table is complete; nothing from a crate's source (the Cargo registry, `~/.cargo/registry`) is needed or allowed. | Item | Where | |---|---| | `std::time::Duration::from_secs(u64) -> Duration` (a `const fn`) | W3 | | `crate::args::ToolName` | W2, W3 | | `UnixStream::set_read_timeout(&self, Option)`, `set_write_timeout` | W5, as before | ## The edits, exactly Nothing else in this file changes. Each quoted "before" block occurs exactly once in the file when its turn comes (make them in order). Keep the fields in the order shown. ### `crates/brokerd/src/memory_port.rs` **W1.** The module's doc comment, the lines ```rust //! Where decided `recall` and `memory` calls go: to `memoryd` over its `brokerd` socket, with the //! session's state, instead of a container (M5a spec, section 8). `Routed` sends every other call //! to the configured runtime. ``` become ```rust //! Where decided `recall`, `memory` and `ask_memory` calls go: to `memoryd` over its `brokerd` //! socket, with the session's state, instead of a container (M5a spec, section 8). `Routed` sends //! every other call to the configured runtime. `ask_memory` has a wait of its own (M5c spec, //! section 9). ``` **W2.** Add one line. The line ```rust use crate::policy::Label; ``` becomes ```rust use crate::args::ToolName; use crate::policy::Label; ``` **W3.** The lines ```rust /// How long `brokerd` waits for `memoryd` to answer one call. pub const MEMORY_WAIT: Duration = Duration::from_secs(30); /// A client of `memoryd`'s `brokerd` socket. One connection per call. pub struct MemoryPort { socket: PathBuf, wait: Duration, } impl MemoryPort { pub fn new(socket: PathBuf, wait: Duration) -> MemoryPort { MemoryPort { socket, wait } } ``` become ```rust /// How long `brokerd` waits for `memoryd` to answer one `recall` or `memory` call. pub const MEMORY_WAIT: Duration = Duration::from_secs(30); /// How long it waits for one `ask_memory` call: `memoryd` asks Honcho's chat about each /// workspace, both at once, with 150 s each (M5c spec, section 9). pub const ASK_MEMORY_WAIT: Duration = Duration::from_secs(170); /// A client of `memoryd`'s `brokerd` socket. One connection per call. pub struct MemoryPort { socket: PathBuf, wait: Duration, ask_wait: Duration, } impl MemoryPort { /// A port that waits `wait` for `recall` and `memory`, and `ASK_MEMORY_WAIT` for `ask_memory`. pub fn new(socket: PathBuf, wait: Duration) -> MemoryPort { MemoryPort { socket, wait, ask_wait: ASK_MEMORY_WAIT, } } /// The same port, waiting `wait` for `ask_memory`. The tests use a short one. pub fn with_ask_wait(self, wait: Duration) -> MemoryPort { MemoryPort { ask_wait: wait, ..self } } /// How long one call of `tool` waits for `memoryd`, to write the call and to read the answer. pub fn wait_for(&self, tool: ToolName) -> Duration { match tool { ToolName::AskMemory => self.ask_wait, _ => self.wait, } } ``` **W4.** In `ask`, the comment's lines ```rust // untrusted from `spec.state()`. 2. `UnixStream::connect(&self.socket)`, then read and // write timeouts of `self.wait`. 3. `write_frame` an Envelope { v: PROTOCOL_VERSION, id: // spec.call().0, final: true, msg: Message::MemoryCall(call) }. 4. `read_frame`; a // Message::ToolResponse is Some(it), any other message None. ``` become ```rust // untrusted from `spec.state()`. 2. `UnixStream::connect(&self.socket)`, then read and // write timeouts of `self.wait_for(spec.tool())`. 3. `write_frame` an Envelope { v: // PROTOCOL_VERSION, id: spec.call().0, final: true, msg: Message::MemoryCall(call) }. 4. // `read_frame`; a Message::ToolResponse is Some(it), any other message None. ``` **W5.** In `ask`, the lines ```rust let mut stream = UnixStream::connect(&self.socket).ok()?; stream.set_read_timeout(Some(self.wait)).ok()?; stream.set_write_timeout(Some(self.wait)).ok()?; ``` become ```rust let wait = self.wait_for(spec.tool()); let mut stream = UnixStream::connect(&self.socket).ok()?; stream.set_read_timeout(Some(wait)).ok()?; stream.set_write_timeout(Some(wait)).ok()?; ``` ## Steps - [ ] **1. Copy the given test.** From the repository root, one command: ```sh cp docs/plans/M5c2/files/crates/brokerd/tests/memory_wait.rs crates/brokerd/tests/ ``` Then `git status --short` lists one new file, `crates/brokerd/tests/memory_wait.rs`. - [ ] **2. See it fail.** `cargo build --tests --keep-going -p brokerd`, with a shell timeout of 600000 ms: one test target, `memory_wait`, does not compile, with 10 errors: one `E0432` (``unresolved import `brokerd::memory_port::ASK_MEMORY_WAIT` ``), six `E0599` (``no method named `wait_for` found for struct `MemoryPort` ``) and three `E0599` (``no method named `with_ask_wait` found for struct `MemoryPort` ``). Every other target compiles. - [ ] **3. The edits.** Make edits W1 to W5, in order. `cargo check -p brokerd` prints no error and no warning. - [ ] **4. The tests.** With a shell timeout of 600000 ms, every test passes: 3 in `memory_wait`, 5 in `memory_port`, 9 in `ask_memory_tool`: ```sh cargo test -p brokerd --test memory_wait --test memory_port --test ask_memory_tool ``` - [ ] **5. Check.** Two commands: - `git diff --stat` lists exactly `crates/brokerd/src/memory_port.rs`. - `grep -n 'MEMORY_WAIT' crates/brokerd/src/main.rs` prints the two lines it printed before this task: the `use` line and `MemoryPort::new(cfg.memory_socket(), MEMORY_WAIT)`. - [ ] **6. Run the gate.** `cargo fmt --all`, then `make gate`, with a shell timeout of 600000 ms. Expected last line: `gate: ok`. The test count is the passed total of `cargo test --workspace` plus one, for the ignored `end_to_end` test that the gate runs on its own: **1384** after this task, 1381 before it; the 3 new ones are the tests of `memory_wait.rs`. To count, add up the `N passed` of every `test result:` line the gate's `cargo test --workspace` printed, and add 1. Never run the gate again only to count. - [ ] **7. Log and commit.** Add your row to `docs/implementer-log.md`; its Task cell is `M5c2/03-brokerd-ask-memory-wait`. `git status --short` lists `memory_port.rs` and your log as modified, and `memory_wait.rs` as new. Stage with `git add crates/brokerd docs/implementer-log.md Cargo.lock`, then commit as `AGENTS.md` says. `Cargo.lock` does not change in this task; the `git add` is harmless. ## Done when - `make gate` prints `gate: ok` with 1384 tests. - `git diff HEAD~1 --stat` lists only `crates/brokerd/src/memory_port.rs`, `crates/brokerd/tests/memory_wait.rs` and `docs/implementer-log.md`. ## Stop and report if - The branch check at the top fails. - A "before" block is not in the file exactly as shown when its turn comes, or is there more than once. - Step 2 shows a different result: another target failing, or other errors. - `cargo check` in step 3 prints an error or a warning. Quote it, and stop. - A test fails in step 4. Do not change the test: stop, and give the failing tests' names and their messages. - The fix seems to need a change anywhere else: `main.rs`, a configuration key, `MemoryPort::new`'s signature, another test. It does not; stop and say why you think so. - A test outside `memory_wait.rs` fails in the gate: check once whether it fails without your change (`git stash -u`, run it, `git stash pop`), then stop and report both results. - The gate passes with a count other than 1384. - A comment in an edit does not fit the code under it. Quote it and stop. ## What the given tests pin Each of these changes to the finished code makes at least one given test fail (10 tried, all caught): either constant changed; `new` giving `ask_memory` its own wait instead of `ASK_MEMORY_WAIT`; `with_ask_wait` doing nothing, or setting the other tools' wait too; `wait_for` giving `ask_memory` the other tools' wait, or `recall` `ask_memory`'s; `ask` using the other tools' wait for every call, or `ask_memory`'s for every call; the read timeout not set.