Files
boxmaker/docs/plans/M5c1/13-memoryd-honcho-edits.md
2026-09-28 09:58:26 -07:00

744 lines
30 KiB
Markdown

# M5c-1 task 13: the Honcho worker sends memory writes
**Branch:** `m5c1` (run `git switch m5c1`; `git status --short` must be empty, otherwise stop)
**Commit subject:** `memoryd: the Honcho worker sends conclusions, their deletions and fleet notes`
**Log row:** the Task cell is `M5c1/13-memoryd-honcho-edits`.
## Goal
Task 12 decides each memory write into the ledger; task 11's worker sends only turns. This task
makes the worker send the rest (M5c spec, sections 4 and 5): one outbox, oldest first, across
every kind of item.
- **`Conclude`**: the conclusion is made in the item's workspace (trusted), about its `peer`
(observer and observed are both that peer), stamped with the Boxmaker session's **memory
session**, `<id>_memory`. That Honcho session holds the agent's peer and the owner's and is
made on first use, whether or not any turn of the session is synced (spec section 4, as of
2026-09-28). Neither peer observes in it: it holds conclusions, not messages, and the spec gives
no flags for it, so both get the agent's (`observe_me: false`, `observe_others: false`). The text
is neutralised as it leaves. `Sent` records the one id Honcho gives.
- **`Unconclude`**: the id to delete is `ledger.conclusion(workspace, text)` (task 07): the latest
sent `Conclude` of that text not yet undone. It is deleted (`delete_conclusion`, task 10: 204 or
404 is done), and `Sent` records the deleted id, which pops it from `conclusion`'s stack. With
none (never sent, its `Conclude` failed, or already undone) nothing is asked: `Sent` with no ids,
and no log line. A `Conclude` still waiting cannot be behind its `Unconclude`: it was decided
first, and the outbox keeps that order.
- **`Fleet`**: the untrusted note is added to the fleet workspace (made with task 10's settings) as
messages from the agent's peer, neutralised, in parts of 25,000 characters, in one batch, to the
Honcho session named after the Boxmaker session, `<id>`, holding only the agent's peer (spec
section 3). `Sent` records the ids.
- **`Retract`**: nothing decides one any more (a note cannot be undone), so it waits in the outbox,
untouched.
- **Errors**, as for turns (task 11): unreachable waits and tries the same item again; refused
marks it failed, logged once; a refused key stops that workspace. Log lines name an edit's item
`part <n> of edit <id>`.
To stay under 500 lines, `worker.rs`'s start code (`Opened`, `open`, `spawn`) moves, unchanged
but for its imports, to a new file `honcho/start.rs`; `worker.rs` re-exports them, so `serve.rs`
does not change.
## Interfaces
In `crates/memoryd/src/honcho/edits.rs` (the module `memoryd::honcho::edits`):
```rust
pub const MEMORY_SUFFIX: &str = "_memory";
pub fn memory_session(session: &SessionId) -> String;
pub fn conclude(clients: &mut Clients, workspace: Workspace, peer: &str, session: &SessionId,
text: &str) -> Result<Vec<String>, HonchoError>;
pub fn unconclude(clients: &Clients, workspace: Workspace, id: Option<&str>)
-> Result<Vec<String>, HonchoError>;
pub fn fleet_batch(text: &str, agent: &str, session: &SessionId, edit: &str) -> Vec<MessageCreate>;
pub fn fleet(clients: &mut Clients, session: &SessionId, messages: &[MessageCreate])
-> Result<Vec<String>, HonchoError>;
```
In `client.rs`: `Clients` keeps `[honcho] owner`, and gains
`ensure_memory_session(&mut self, workspace: Workspace, name: &str) -> Result<(), HonchoError>`
(edits C1 to C3). In `worker.rs`, the private `Next` holds a `Job` (`Turn`, `Conclude`,
`Unconclude`, `Fleet`), `next` builds it for any kind of item, and `send` dispatches on it
(edits W1 to W7).
## The requests
Fixture 11 is the conclusion's shape:
| Item | Requests |
|---|---|
| `Conclude` | make sure of the workspace, the agent's peer, the owner's peer and `<id>_memory` (`{"id":"<id>_memory","peers":{"<agent>":{"observe_me":false,"observe_others":false},"<owner>":{"observe_me":false,"observe_others":false}}}`), then `POST /v3/workspaces/<trusted>/conclusions` with `{"conclusions":[{"content":"<text>","observer_id":"<peer>","observed_id":"<peer>","session_id":"<id>_memory"}]}` |
| `Unconclude` | `DELETE /v3/workspaces/<trusted>/conclusions/<id>`, or none |
| `Fleet` | make sure of the fleet workspace, the agent's peer and `<id>` (`{"id":"<id>","peers":{"<agent>":{"observe_me":false,"observe_others":false}}}`), then `POST /v3/workspaces/<fleet>/sessions/<id>/messages` with each part `{"content":"<part>","peer_id":"<agent>","metadata":{"agent":"<agent>","boxmaker_session":"<id>","edit":"<edit id>"}}` |
What was made sure of is remembered for the run (task 10), so a second conclusion in the same
session asks only for the conclusion.
## Every place that changes
This is the complete list.
| Place | What happens there |
|---|---|
| `crates/memoryd/src/honcho/mod.rs` | `pub mod edits;` and `pub mod start;` (edits H1, H2) |
| `crates/memoryd/src/honcho/client.rs` | `owner`, `ensure_memory_session` (edits C1 to C3) |
| `crates/memoryd/src/honcho/edits.rs` | new: a skeleton you copy, then fill 5 bodies |
| `crates/memoryd/src/honcho/start.rs` | new, given whole: the start code from `worker.rs` |
| `crates/memoryd/src/honcho/worker.rs` | imports, `Job` and `Next`, `label`, the two log lines, `next`/`send`/`send_turn`, the start code removed (edits W1 to W7) |
| `crates/memoryd/tests/support/honcho.rs` | a new version, given: the fake answers a conclusion batch (fixture 11), and a batch with as many items as it asked for |
| `crates/memoryd/tests/honcho_worker_more.rs` | a new version, given: its "later actions wait" test is now about a retraction |
| everything else | **no change** |
## Files
(A copy command names `<file>-taskNN` where a later task gives a newer version of that file:
this task's version is the one named.)
- Copy, from `docs/plans/M5c1/files/` (the same path under it):
- the new test `crates/memoryd/tests/honcho_edits.rs`
- **new versions** (they replace the files in the tree) of
`crates/memoryd/tests/support/honcho.rs` and `crates/memoryd/tests/honcho_worker_more.rs`;
do not copy `honcho.rs-task10` or `honcho_worker_more.rs-task11`, the older ones
- the skeleton `crates/memoryd/src/honcho/edits.rs` and the file
`crates/memoryd/src/honcho/start.rs` (given whole: nothing to fill)
- Modify: the files of edits H1 to W7, `docs/implementer-log.md`.
- Do not touch: any other source file (`serve.rs` in particular), any other test or support file,
the fixtures, any `Cargo.toml`, and in the skeleton anything but the 5 `todo!()` bodies.
## Facts you need
- **`roncho`'s conclusion call** (`roncho::blocking::Client`): `create_conclusions(&ConclusionBatchCreate)
-> Result<Vec<roncho::Conclusion>, Error>` (`POST .../conclusions`; a batch of 1 to 100, checked
before sending). `ConclusionCreate::new(content, observer_id, observed_id)`,
`.with_session_id(name)`; `ConclusionBatchCreate::new(vec![..])`. `Conclusion.id` is the id.
Honcho's `src/schemas/api.py` caps a conclusion batch at 100, as it does messages.
- **Task 07's `conclusion`** replays the `Sent` records in order: a sent `Conclude` of a workspace
and text pushes `(item, ids[0])`; a sent `Unconclude` of the same pops. So an `Unconclude`'s
`Sent` pops whether it carries the deleted id or none; the id is kept for the record.
- **Session names.** `_` is allowed in Honcho names and in no Boxmaker session id, so
`<id>_memory` is never a turn session (`<id>`, `<id>-2`, …) or another session's id.
- **The given code** of the worker, after W6: `next` matches `(item, action)` pairs; an
`Unconclude`'s id is looked up there, under the ledger's lock, when the item is picked;
`send` calls `send_turn` (task 11's turn code, unchanged but for its arguments) or `edits`.
- **The given tests.**
- `honcho_edits.rs` (10 tests): the memory session's name; a conclusion's five requests, exactly,
its id, and `conclusion`; a replace and its undo (the requests in order, the deleted ids, each
thing made sure of once); an `Unconclude` with nothing to delete (never sent, or its
`Conclude` failed); a 404 on delete; a fleet note's four requests, exactly, and a long note in
parts; a note too long for one batch; neutralising; one outbox across a turn, an edit and a
turn, and a restart that sends nothing; errors (an outage, a 422, a refused fleet key that
leaves the trusted workspace going), with no text in a log line.
- `honcho_worker_more.rs` (10 tests): task 11's, with its "later actions" test about a
retraction, which waits.
- The calls the bodies use:
| Call | Signature, and what it gives |
|---|---|
| `Clients::ensure_memory_session` | edit C3: `(&mut self, workspace, name: &str) -> Result<(), HonchoError>` |
| `Clients::ensure_session`, `delete_conclusion`, `client` | task 10 |
| `ConclusionCreate`, `ConclusionBatchCreate` | above |
| `Client::create_conclusions`, `add_messages` | above; task 11 |
| `neutralise` | `bxcore::sanitize::neutralise(text) -> Cow<'_, str>`; `ConclusionCreate::new` takes it as `impl Into<String>` |
| `worker::parts` | task 11: parts of at most 25,000 characters |
| `classify` | task 10: `roncho`'s error as a `HonchoError` |
## The edits, exactly
Nothing else in these files changes. Each quoted "before" block occurs exactly once in its file.
Edit W6 replaces `next` and `send` as they are after task 12 (`next`'s body is yours from task
11, with task 12's W5); edit W7 removes the start code, which moves to `start.rs`. If either
"before" block differs from your file, stop and report.
### `crates/memoryd/src/honcho/worker.rs`
**W1.** The lines
```rust
use crate::config::Config;
use crate::honcho::client::{Clients, HonchoError, Timeouts, classify};
use crate::honcho::config::HonchoConfig;
use crate::honcho::decide::{final_reply, turn_records, user_text};
use crate::honcho::ledger::{self, Action, Item, Ledger, Record, Workspace};
use crate::honcho::mirror::Mirror;
use crate::honcho::naming::{HonchoSession, honcho_session};
use crate::honcho::state;
use crate::honcho::turns::{LEDGER, TurnSync, log_path, read_log};
use crate::serve::{Log, StartError};
```
become
```rust
use crate::honcho::client::{Clients, HonchoError, classify};
use crate::honcho::decide::{final_reply, turn_records, user_text};
use crate::honcho::edits;
use crate::honcho::ledger::{self, Action, Item, Ledger, Record, Workspace};
use crate::honcho::naming::{HonchoSession, honcho_session};
use crate::honcho::turns::{TurnSync, log_path, read_log};
use crate::serve::Log;
pub use crate::honcho::start::{Opened, open, spawn};
```
**W2.** The lines
```rust
/// A turn to send: its item, workspace, speaker and Honcho session.
struct Next {
item: Item,
session: SessionId,
turn: u64,
workspace: Workspace,
speaker: String,
honcho: Option<HonchoSession>,
}
```
become
```rust
/// What to send for one waiting item (M5c spec, section 5).
enum Job {
/// A turn: its session and number, its speaker, and its Honcho session.
Turn {
session: SessionId,
turn: u64,
speaker: String,
honcho: Option<HonchoSession>,
},
/// A conclusion about `peer`, stamped with `session`'s memory session.
Conclude {
peer: String,
session: SessionId,
text: String,
},
/// Delete the conclusion with this id; `None` when no sent conclusion is live.
Unconclude { id: Option<String> },
/// An untrusted note: a fleet message from the agent.
Fleet {
session: SessionId,
edit: String,
text: String,
},
}
/// The oldest waiting item: what it is, where it goes, and what to send.
struct Next {
item: Item,
workspace: Workspace,
job: Job,
}
```
**W3.** The line
```rust
/// The ids of the messages Honcho made, in order.
```
becomes
```rust
/// An item as the log lines name it: `turn 3 of session mm-a`, `part 0 of edit 2026-09-28-0001`.
fn label(item: &Item) -> String {
match item {
Item::Turn { session, turn } => format!("turn {turn} of session {}", session.as_str()),
Item::Edit { id, part } => format!("part {part} of edit {id}"),
}
}
/// The ids of the messages Honcho made, in order.
```
**W4.** In `step`, in the arm `Err(Unsent::Honcho(e))`, the lines
```rust
"memoryd: turn {} of session {} was not sent: {e}; it is marked failed; {SEND_REFUSED}",
next.turn,
next.session.as_str()
```
become
```rust
"memoryd: {} was not sent: {e}; it is marked failed; {SEND_REFUSED}",
label(&next.item)
```
**W5.** In `step`, in the arm `Err(Unsent::Local(kind))`, the lines
```rust
"memoryd: turn {} of session {} cannot be sent ({kind}); it is marked failed; {pointer}",
next.turn,
next.session.as_str()
```
become
```rust
"memoryd: {} cannot be sent ({kind}); it is marked failed; {pointer}",
label(&next.item)
```
**W6.** The lines (the whole of `next` and `send`, from `next`'s doc comment to the line before `unreachable`'s)
```rust
/// The oldest waiting turn whose workspace's key was not refused. Other actions wait.
fn next(&self) -> Option<Next> {
let ledger = self.ledger.lock().unwrap_or_else(|p| p.into_inner());
ledger.outbox().into_iter().find_map(|(item, action)| {
let Item::Turn { session, turn } = &item else {
return None;
};
let Action::Turn { workspace, speaker } = action else {
return None;
};
if self.refused.contains(&workspace) {
return None;
}
Some(Next {
honcho: honcho_session(ledger.decisions(), session, *turn),
session: session.clone(),
turn: *turn,
item: item.clone(),
workspace,
speaker,
})
})
}
/// Read the turn again, make sure of its Honcho session, and add its messages as one batch.
fn send(&mut self, next: &Next) -> Result<Vec<String>, Unsent> {
let (text, reply) = read_turn(&log_path(&self.sessions, &next.session), next.turn)
.ok_or(Unsent::Local("log"))?;
let honcho = next.honcho.as_ref().ok_or(Unsent::Local("log"))?;
let messages = batch(
&text,
&reply,
&next.speaker,
&self.agent,
&next.session,
next.turn,
);
if messages.len() > MAX_BATCH {
return Err(Unsent::Local("too-long"));
}
self.clients
.ensure_session(next.workspace, honcho)
.map_err(Unsent::Honcho)?;
let client = self.clients.client(next.workspace);
match client.add_messages(&honcho.name, &messages) {
Ok(made) => Ok(ids(&made)),
Err(e) => Err(Unsent::Honcho(classify(&e))),
}
}
```
become
```rust
/// The oldest waiting item whose workspace's key was not refused. A retraction, which
/// nothing decides any more, waits.
fn next(&self) -> Option<Next> {
let ledger = self.ledger.lock().unwrap_or_else(|p| p.into_inner());
ledger.outbox().into_iter().find_map(|(item, action)| {
let (workspace, job) = match (&item, action) {
(Item::Turn { session, turn }, Action::Turn { workspace, speaker }) => (
workspace,
Job::Turn {
honcho: honcho_session(ledger.decisions(), session, *turn),
session: session.clone(),
turn: *turn,
speaker,
},
),
(
Item::Edit { .. },
Action::Conclude {
workspace,
peer,
session,
text,
},
) => (
workspace,
Job::Conclude {
peer,
session,
text,
},
),
(Item::Edit { .. }, Action::Unconclude { workspace, text }) => (
workspace,
Job::Unconclude {
id: ledger.conclusion(workspace, &text).map(|(_, id)| id),
},
),
(Item::Edit { id, .. }, Action::Fleet { session, text }) => (
Workspace::Fleet,
Job::Fleet {
session,
edit: id.clone(),
text,
},
),
_ => return None,
};
if self.refused.contains(&workspace) {
return None;
}
Some(Next {
item: item.clone(),
workspace,
job,
})
})
}
/// Send one item, by what it is.
fn send(&mut self, next: &Next) -> Result<Vec<String>, Unsent> {
let honcho = |e| Unsent::Honcho(e);
match &next.job {
Job::Turn {
session,
turn,
speaker,
honcho: name,
} => self.send_turn(next.workspace, session, *turn, speaker, name.as_ref()),
Job::Conclude {
peer,
session,
text,
} => edits::conclude(&mut self.clients, next.workspace, peer, session, text)
.map_err(honcho),
Job::Unconclude { id } => {
edits::unconclude(&self.clients, next.workspace, id.as_deref()).map_err(honcho)
}
Job::Fleet {
session,
edit,
text,
} => {
let messages = edits::fleet_batch(text, &self.agent, session, edit);
if messages.len() > MAX_BATCH {
return Err(Unsent::Local("too-long"));
}
edits::fleet(&mut self.clients, session, &messages).map_err(honcho)
}
}
}
/// Read the turn again, make sure of its Honcho session, and add its messages as one batch.
fn send_turn(
&mut self,
workspace: Workspace,
session: &SessionId,
turn: u64,
speaker: &str,
honcho: Option<&HonchoSession>,
) -> Result<Vec<String>, Unsent> {
let (text, reply) =
read_turn(&log_path(&self.sessions, session), turn).ok_or(Unsent::Local("log"))?;
let honcho = honcho.ok_or(Unsent::Local("log"))?;
let messages = batch(&text, &reply, speaker, &self.agent, session, turn);
if messages.len() > MAX_BATCH {
return Err(Unsent::Local("too-long"));
}
self.clients
.ensure_session(workspace, honcho)
.map_err(Unsent::Honcho)?;
let client = self.clients.client(workspace);
match client.add_messages(&honcho.name, &messages) {
Ok(made) => Ok(ids(&made)),
Err(e) => Err(Unsent::Honcho(classify(&e))),
}
}
```
**W7.** At the end of the file, the lines (the end of `impl Worker`, then the start code)
```rust
}
/// With `[honcho]`, at start: the ledger read through, the sessions that wait again, and the
/// clients with their keys read. Nothing is bound or started yet.
pub struct Opened {
pub sync: Arc<Mutex<TurnSync>>,
clients: Clients,
agent: String,
owner: String,
}
impl Opened {
/// The memory writes' side of the ledger (task 12).
pub fn mirror(&self) -> Mirror {
let ledger = self
.sync
.lock()
.unwrap_or_else(|p| p.into_inner())
.shared_ledger();
Mirror::new(ledger, &self.agent, &self.owner)
}
}
/// Open the ledger (a cut-off last line is logged), resume the waiting sessions, and build the
/// clients (their key files' warnings are logged). A key that cannot be read is `Secret`.
pub fn open(config: &Config, honcho: &HonchoConfig, log: &Log) -> Result<Opened, StartError> {
let path = config.memory_dir().join(LEDGER);
let ledger = Ledger::open(&path).map_err(StartError::Ledger)?;
if ledger.repaired() {
(log)(&format!(
"memoryd: {}: a last line cut off by a crash was dropped",
path.display()
));
}
let mut sync = TurnSync::new(ledger, &honcho.agent, &config.sessions_dir());
sync.resume();
let env = |name: &str| std::env::var_os(name);
let (clients, warnings) =
Clients::new(honcho, &env, Timeouts::WRITES).map_err(StartError::Secret)?;
for warning in warnings {
(log)(&warning);
}
Ok(Opened {
sync: Arc::new(Mutex::new(sync)),
clients,
agent: honcho.agent.clone(),
owner: honcho.owner.clone(),
})
}
/// Start the worker's thread, asking `brokerd` on `config`'s state socket.
pub fn spawn(opened: Opened, config: &Config, wake: Arc<Wake>, log: Log) -> Result<(), String> {
let socket = config.state_socket();
let ask: AskBox =
Box::new(move |session: &SessionId| state::ask(&socket, session, state::WAIT));
let worker = Worker::new(
opened.sync,
opened.clients,
wake,
ask,
log,
Tuning::DEFAULT,
&opened.agent,
);
std::thread::Builder::new()
.name("honcho".to_string())
.spawn(move || worker.run())
.map(|_| ())
.map_err(|e| format!("cannot start the Honcho worker: {e}"))
}
```
become
```rust
}
```
### `crates/memoryd/src/honcho/client.rs`
**C1.** In `pub struct Clients`, the lines
```rust
fleet: Client,
agent: String,
```
become
```rust
fleet: Client,
agent: String,
owner: String,
```
**C2.** In `Clients::new`, the lines
```rust
agent: config.agent.clone(),
ensured: HashSet::new(),
```
become
```rust
agent: config.agent.clone(),
owner: config.owner.clone(),
ensured: HashSet::new(),
```
**C3.** The line
```rust
/// Delete conclusion `id` in `workspace`. Gone already (404) counts as done.
```
becomes
```rust
/// Make sure memory session `name` (a Boxmaker session's `<id>_memory`) exists in
/// `workspace`, after the workspace, the agent's peer and the owner's. It holds conclusions,
/// no messages, so neither peer observes (M5c spec, section 4).
pub fn ensure_memory_session(
&mut self,
workspace: Workspace,
name: &str,
) -> Result<(), HonchoError> {
self.ensure_workspace(workspace)?;
let (agent, owner) = (self.agent.clone(), self.owner.clone());
self.ensure_peer(workspace, &agent)?;
self.ensure_peer(workspace, &owner)?;
let key = (workspace, Ensured::Session(name.to_string()));
if self.ensured.contains(&key) {
return Ok(());
}
let unobserved = || {
SessionPeerConfig::new()
.observe_me(false)
.observe_others(false)
};
let mut peers = HashMap::new();
peers.insert(agent, unobserved());
peers.insert(owner, unobserved());
let create = SessionCreate::new(name).with_peers(peers);
created(self.client(workspace).session(&create))?;
self.ensured.insert(key);
Ok(())
}
/// Delete conclusion `id` in `workspace`. Gone already (404) counts as done.
```
### `crates/memoryd/src/honcho/mod.rs`
**H1.** The line
```rust
pub mod decide;
```
becomes
```rust
pub mod decide;
pub mod edits;
```
**H2.** The line
```rust
pub mod naming;
```
becomes
```rust
pub mod naming;
pub mod start;
```
## Steps
- [ ] **1. Copy the given tests.** From the repository root, three commands:
```sh
cp docs/plans/M5c1/files/crates/memoryd/tests/honcho_edits.rs-task13 crates/memoryd/tests/honcho_edits.rs
cp docs/plans/M5c1/files/crates/memoryd/tests/honcho_worker_more.rs-task13 crates/memoryd/tests/honcho_worker_more.rs
cp docs/plans/M5c1/files/crates/memoryd/tests/support/honcho.rs crates/memoryd/tests/support/
```
- [ ] **2. See them fail.** With a shell timeout of 600000 ms:
- `cargo build --tests --keep-going -p memoryd`: one test target does not compile,
`honcho_edits`, with one error, `E0432` (``unresolved import `memoryd::honcho::edits` ``).
Every other target compiles.
- `cargo test -p memoryd --test honcho_worker_more --test honcho_client --test honcho_worker`
passes (10, 13, 12): the new fake and the retraction test agree with the code as it is.
- [ ] **3. The new files.** Make edits H1, H2 and C1 to C3, then copy:
```sh
cp docs/plans/M5c1/files/crates/memoryd/src/honcho/edits.rs crates/memoryd/src/honcho/
cp docs/plans/M5c1/files/crates/memoryd/src/honcho/start.rs crates/memoryd/src/honcho/
```
`cargo check -p memoryd`: no error, and 22 warnings, all from the unfilled bodies: six
`unused import` lines (`bxcore::sanitize::neutralise`; `classify`;
`crate::honcho::naming::HonchoSession`; `crate::honcho::worker::parts`;
`ConclusionBatchCreate` and `ConclusionCreate`; `Map` and `json`), and sixteen `unused
variable` (`session` four times, `clients` three times, `text` and `workspace` twice each, and
`agent`, `edit`, `id`, `messages`, `peer` once each).
- [ ] **4. Fill the five bodies of `edits.rs`, one at a time, in the order of the file**, each
from its comment, and run `cargo check -p memoryd` after each: `memory_session`, `conclude`,
`unconclude`, `fleet_batch`, `fleet`. When a body is filled, delete its instruction comment
(the `//` lines inside the body) and keep the function's doc comment (`///`) as it is. After
the fifth, `cargo check -p memoryd` prints no warning.
- [ ] **5. The worker.** Make edits W1 to W7. `cargo check -p memoryd`: no error and no warning.
`wc -l crates/memoryd/src/honcho/worker.rs` prints a number under 500.
- [ ] **6. The tests.** With a shell timeout of 600000 ms, and every test passes (10, 10, 12, and
the earlier ones unchanged):
```sh
cargo test -p memoryd --test honcho_edits --test honcho_worker_more --test honcho_worker \
--test honcho_client --test honcho_keys --test honcho_mirror_serve --test honcho_serve
```
- [ ] **7. Check.** Three commands:
- `grep -n 'fn open\|fn spawn' crates/memoryd/src/honcho/worker.rs
crates/memoryd/src/honcho/start.rs` prints two lines, both in `start.rs`.
- `grep -n 'Action::Retract' crates/memoryd/src/honcho/worker.rs` prints nothing: a retraction
waits.
- `git diff --stat -- crates/bxcore crates/loopd crates/brokerd crates/gatewayd crates/bxctl
Cargo.toml Cargo.lock crates/memoryd/Cargo.toml crates/memoryd/src/serve.rs
crates/memoryd/src/honcho/ledger.rs crates/memoryd/src/honcho/mirror.rs` prints nothing.
- [ ] **8. Run the gate.** `cargo fmt --all`, then `make gate`, with a shell timeout of 600000 ms.
Expected last line: `gate: ok`, with 1324 tests: 1314 before this task, plus 10 new ones in
`honcho_edits.rs`.
- [ ] **9. Log and commit.** Add your row to `docs/implementer-log.md`; its Task cell is
`M5c1/13-memoryd-honcho-edits`. `git status --short` lists `honcho/mod.rs`, `client.rs`,
`worker.rs`, `tests/support/honcho.rs`, `tests/honcho_worker_more.rs` and your log as modified,
and `edits.rs`, `start.rs` and `tests/honcho_edits.rs` as new. Stage with
`git add crates/memoryd 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 1324 tests.
- `git diff HEAD~1 --stat` lists only the files of step 9.
## Stop and report if
- A "before" block of H1, H2, C1 to C3 or W1 to W7 is not in its file exactly as shown, or is
there more than once.
- Step 2 or 3 shows a different result: other test targets, errors of another kind, other
warnings, or other tests failing.
- `cargo check` reports an error after a body you filled or an edit you made, and the comment or
the edit does not tell you how to fix it. Quote the error, and stop.
- A comment in the skeleton does not fit the code under it, or disagrees with this task's tables.
Quote both and stop.
- After step 4 or 6 a test fails although each body follows its comment and each edit is as
shown. Do not change a test, the fake or the given code: stop, and give the failing tests'
names and their messages.
- A test seems to need a real Honcho or a new fixture. It does not; stop and say why.
- The fix seems to need a change anywhere else: `serve.rs`, `ledger.rs`, `mirror.rs`, another
crate. It does not; stop and say why you think so.
- A test outside the new files fails in the gate: check once whether it fails without your
change, then stop and report both results. (`gatewayd`'s `gateway_sock` test
`brokerd_closing_first_ends_it_as_no_longer_pending` fails now and then on a loaded machine,
without this task's change; if it is the one, run the gate once more.)
- The gate passes with a count other than 1324.
## What the given tests pin
Each of these changes to the finished code makes at least one given test fail (23 tried): the
suffix; a conclusion not neutralised, observed by the agent, not stamped, or without its memory
session made first; its id dropped; an `Unconclude` recording no id, or deleting something when
there is nothing; a fleet note not neutralised, from the speaker, without its edit in the
metadata, its session with a speaker or not made first, or over the batch limit; the memory
session's owner observed, left out, or not remembered; edits left waiting; an `Unconclude`
looked up in another workspace; a fleet note sent to the trusted workspace; a retraction sent;
an edit's log label; a refused workspace ignored for edits.
## After this task
- Task 14: `bxctl memory sync` shows the ledger's counts and its failed items.
- Task 15: the cutoff, turns from before Honcho was turned on skipped.