M5c-1 plan: task 13, the Honcho worker sends memory writes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-28 09:27:51 -07:00
co-authored by Claude Opus 5.5
parent d3d37c955a
commit 687d07a9a7
10 changed files with 1938 additions and 14 deletions
@@ -0,0 +1,78 @@
//! Memory writes sent to Honcho (M5c spec, sections 4 and 5), for the worker: a conclusion made
//! in the Boxmaker session's memory session, `<id>_memory`; a conclusion deleted by the id its
//! making was given; an untrusted note added to the fleet workspace as a message from the agent.
//! The text is neutralised here, as it leaves.
use bxcore::SessionId;
use bxcore::sanitize::neutralise;
use roncho::{ConclusionBatchCreate, ConclusionCreate, MessageCreate};
use serde_json::{Map, json};
use crate::honcho::client::{Clients, HonchoError, classify};
use crate::honcho::ledger::Workspace;
use crate::honcho::naming::HonchoSession;
use crate::honcho::worker::parts;
/// What names a Boxmaker session's memory session: `_` is in no Boxmaker id, so the name is no
/// other session's.
pub const MEMORY_SUFFIX: &str = "_memory";
/// The memory session of `session`: `<id>_memory`.
pub fn memory_session(session: &SessionId) -> String {
// `format!("{}{MEMORY_SUFFIX}", session.as_str())`.
todo!()
}
/// Make conclusion `text` about `peer` (its observer and the observed) in `workspace`, stamped
/// with `session`'s memory session, which is made sure of first. Its one id.
pub fn conclude(
clients: &mut Clients,
workspace: Workspace,
peer: &str,
session: &SessionId,
text: &str,
) -> Result<Vec<String>, HonchoError> {
// `let name = memory_session(session);`, then
// `clients.ensure_memory_session(workspace, &name)?;`. The conclusion:
// `ConclusionCreate::new(neutralise(text), peer, peer).with_session_id(name)` (observer and
// observed both `peer`), in `ConclusionBatchCreate::new(vec![conclusion])`. Then `match
// clients.client(workspace).create_conclusions(&batch)`: `Ok(made)` ->
// `Ok(made.into_iter().map(|c| c.id).collect())`; `Err(e)` -> `Err(classify(&e))`.
todo!()
}
/// Delete conclusion `id` in `workspace`; with none to delete, nothing is asked. The ids of what
/// was deleted.
pub fn unconclude(
clients: &Clients,
workspace: Workspace,
id: Option<&str>,
) -> Result<Vec<String>, HonchoError> {
// `match id`: `Some(id)` -> `clients.delete_conclusion(workspace, id)?;` then
// `Ok(vec![id.to_string()])`; `None` -> `Ok(Vec::new())`: nothing is asked.
todo!()
}
/// An untrusted note's messages for the fleet workspace: from the agent's peer, neutralised, in
/// parts of at most `MAX_PART` characters, each with the note's metadata.
pub fn fleet_batch(text: &str, agent: &str, session: &SessionId, edit: &str) -> Vec<MessageCreate> {
// A `Map` `metadata` with `"agent"` -> `json!(agent)`, `"boxmaker_session"` ->
// `json!(session.as_str())`, `"edit"` -> `json!(edit)` (in this order). Then
// `parts(&neutralise(text)).into_iter().map(|part| MessageCreate::new(part, agent)
// .with_metadata(metadata.clone())).collect()`.
todo!()
}
/// Add `messages` to `session`'s Honcho session in the fleet workspace, which holds only the
/// agent's peer, made sure of first. The ids of the messages.
pub fn fleet(
clients: &mut Clients,
session: &SessionId,
messages: &[MessageCreate],
) -> Result<Vec<String>, HonchoError> {
// `let honcho = HonchoSession { name: session.as_str().to_string(), speakers: Vec::new() };`
// then `clients.ensure_session(Workspace::Fleet, &honcho)?;`. Then `match
// clients.client(Workspace::Fleet).add_messages(&honcho.name, messages)`: `Ok(made)` ->
// `Ok(made.into_iter().map(|m| m.id).collect())`; `Err(e)` -> `Err(classify(&e))`.
todo!()
}
@@ -0,0 +1,85 @@
//! `memoryd`'s Honcho side at start (M5c spec, sections 5 and 10): the ledger read through, the
//! sessions that wait again, the clients with their keys, and the worker's thread.
use std::sync::{Arc, Mutex};
use bxcore::SessionId;
use crate::config::Config;
use crate::honcho::client::{Clients, Timeouts};
use crate::honcho::config::HonchoConfig;
use crate::honcho::ledger::Ledger;
use crate::honcho::mirror::Mirror;
use crate::honcho::state;
use crate::honcho::turns::{LEDGER, TurnSync};
use crate::honcho::worker::{AskBox, Tuning, Wake, Worker};
use crate::serve::{Log, StartError};
/// 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}"))
}