Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
744 lines
30 KiB
Markdown
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.
|