roncho moved twice while the requirements were written. Line numbers are gone; each divergence is described by what the code does, rechecked at a9708f1, which added the Workspaces API: O1 and O7 now name the async functions, and workspace delete does not tell the 409 conflict apart. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
15 KiB
roncho: what Boxmaker's memoryd needs from it
For: the agent that develops roncho (git.wntrmute.dev/kyle/roncho, ~/src/roncho on titan).
From: Boxmaker's design model, 2026-09-25, for milestone M5b. Status: requirements, for the
owner to hand over. Boxmaker's M5b spec is written against the release this produces.
roncho was being reworked while this was written: the divergences in 3.4 were last checked at
a9708f1 and are described by behaviour, not by line. Check each one before fixing it.
1. Why
Boxmaker is the owner's personal agent harness (Rust, ~/src/boxmaker). Its memory daemon,
memoryd, will sync finished conversation turns to the owner's Honcho on rift, write some
entries as conclusions, search Honcho, and ask its dialectic. The owner wants it to do that
through roncho, not through a second client. Today roncho cannot be used from Boxmaker:
- It is async only (tokio, reqwest, hyper, futures). Boxmaker has a standing rule: blocking I/O
with threads, no async runtime, and
memorydis the smallest-authority daemon of the set. - Some of its request and response shapes disagree with the live server. sift's Go client,
checked against rift on 2026-09-22, found that workspace search takes
{"query", "limit"}and returns a bare JSON array, not aPage(~/src/sift/internal/fleetctx/fleetctx.go,~/src/sift/docs/P2.md). - It defaults to
https://api.honcho.dev. For Boxmaker, memory text must never leave the owner's hosts, so a default that points at a cloud service is a hazard, not a convenience.
Boxmaker's policy (which turns are synced, trust labels, taint) stays in memoryd. roncho is
the transport and the types: correct, small, and predictable.
2. The deployment it must work against
| Fact | Value | Source |
|---|---|---|
| Honcho API | http://rift.scylla-hammerhead.ts.net:8000, plain HTTP over the tailnet |
imladris/hw/orion/default.nix; sift/docs/PLAN.md |
| Version | 3.2.0, image 3.2.0-pr1092-7e57fe2 (roncho's SPEC targets 3.2.1) |
imladris/docs/metrics-plan.md |
| Authentication | apparently none: sift sends no Authorization header and succeeds. Not confirmed. |
sift/internal/fleetctx/fleetctx.go |
| Models | deriver, summaries and dialectic on dixie's ornith-1.5-9b-uncensored; dialectic answers take 30 to 50 s |
imladris/hw/dixie/default.nix; sift/docs/PLAN.md |
| Workspaces in use | hermes-fleet, guardian-star (other agents'); each Boxmaker will have its own |
imladris/hw/orion/default.nix |
Never write to a workspace you did not create for testing. hermes-fleet and
guardian-star hold other agents' live memory. Write tests go to a throwaway workspace (for
example roncho-test-<date>), deleted afterwards.
3. Requirements
MUST: Boxmaker cannot use roncho without it. SHOULD: Boxmaker can work around it, at a cost.
3.1 A blocking client with a small dependency graph
- R1 (MUST) A blocking API: every operation in 3.3 as a plain function call that returns when the response is read. No async runtime is started, required or depended on.
- R2 (MUST) Selected by Cargo features so a consumer can build only the blocking client:
roncho = { …, default-features = false, features = ["blocking"] }(the name is yours). With that, the dependency graph contains none oftokio,reqwest,hyper,h2,futures,async-stream,bytes,webpki-roots. The async client may stay as the default feature. - R3 (MUST) HTTP/1.1 over plain TCP for
http://URLs, written onstd::netor with a small blocking crate. Boxmaker already has two such clients (crates/loopd/src/http.rs,crates/gatewayd/src/http.rs): one request per connection,Connection: close, chunked andContent-Lengthbodies. - R4 (SHOULD)
https://behind an optional feature, usingrustls0.23 with theringprovider and the platform's certificates (rustls-native-certs0.8). Notwebpki-roots, notaws-lc-rs. rift needs no TLS today, so this may come after the rest. - R5 (MUST) Every crate in the blocking graph (with and without R4) is on crates.io, is
licensed under one of MIT, Apache-2.0, ISC, BSD-3-Clause, Unicode-3.0 or Zlib, and does not
duplicate a different major version of a crate Boxmaker already locks. Boxmaker locks today:
serde1.0.229,serde_json1.0.151,rustls0.23.45,ring0.17.14,rustls-native-certs0.8.4,thiserror2.0.21,syn3,toml1.1.6,zeroize1.9.0. Boxmaker'scargo denyruns withmultiple-versions = "deny". - R6 (SHOULD) Keep the blocking graph small: beyond
serdeandserde_json(and R4's TLS crates), add as few crates as you can. Prefer timestamps as RFC 3339 strings, ids as strings and URLs parsed by hand overchrono,uuidandurlin the blocking models. If you keep any of them, usedefault-features = false(forchrono, noclock, which pulls platform time-zone crates) and say why in the README.thiserroris acceptable: Boxmaker already locks it. - R7 (MUST) Builds and passes its tests with Rust 1.95 (Boxmaker's
rust-version). Setrust-versioninCargo.toml. - R8 (MUST) A git tag per release that Boxmaker can pin (
revortag), and a changelog entry saying what changed in the blocking API. Boxmaker will add the git source to itsdeny.tomlallow-list for this one URL; publishing to crates.io instead is fine too.
3.2 Configuration and safety
- R9 (MUST) No default base URL in the blocking client. Building a client without one is an error. (Keep the async default if you like; the blocking path must not have one.)
- R10 (MUST) The API key is optional. When set, it is sent as
Authorization: Bearer; when not, noAuthorizationheader at all. Building without a key is not an error. The key never appears in an error, aDebugoutput or a log line. - R11 (MUST) The workspace is set explicitly per client. A client is cheap to clone and one
client serves one workspace, as today. A client is
Send + Sync. - R12 (MUST) The blocking client takes its configuration only from the caller. It reads no
environment variables: not
HONCHO_*(todaybuild()falls back toHONCHO_WORKSPACE_ID,HONCHO_API_KEYandHONCHO_BASE_URL), not proxy settings (HTTP_PROXY,ALL_PROXYand so on). It does not follow redirects: a 3xx is returned as an error. Memory text is sent only to the configured host. - R13 (MUST) Timeouts are set by the caller, per client: a connect timeout, and a read
timeout that means "no bytes for N seconds", not a total deadline. A dialectic answer takes 30
to 50 s on the current hardware, so the caller will set a long one there. No hidden retries:
either implement
max_retriesfor idempotent calls with a documented policy, or remove it. The blocking client defaults to no retries. - R14 (MUST) A cap on the size of a response body, set by the caller, with a sensible default (for example 8 MiB). A body over the cap is an error, and reading stops at the cap.
- R15 (MUST) No background threads, no global state, no logging of request or response content, no telemetry, no update checks.
3.3 Operations the blocking API provides
Each operation is verified against rift (3.4) before it is released. Names are suggestions.
| # | Operation | Honcho endpoint (as roncho has it today; verify) | Boxmaker's use |
|---|---|---|---|
| O1 | get or create the workspace | POST /v3/workspaces (in the async client as get_or_create_workspace) |
first start |
| O2 | get or create a peer, with metadata and configuration | POST peers |
the owner, the agent, other agents met in channels |
| O3 | get or create a session, with its peers and each peer's observe_me / observe_others |
POST sessions |
one Honcho session per Boxmaker session |
| O4 | add messages in a batch, each with peer_id, content, metadata (a JSON object) and created_at |
POST sessions/{s}/messages |
the sync of finished turns; the metadata holds Boxmaker's session id, record number and labels |
| O5 | list a session's messages, paged, with filters | POST sessions/{s}/messages/list |
checking what a sync already sent |
| O6 | delete a session | DELETE sessions/{s} |
rebuilding |
| O7 | delete the workspace, and report the "active sessions" conflict (409) as its own error | DELETE /v3/workspaces/{id} (the async delete_workspace returns it as a generic error) |
rebuilding |
| O8 | create conclusions in a batch (1 to 100), content, observer_id, observed_id, optional session_id |
POST conclusions |
entries moved out of identity; trusted notes |
| O9 | list, query and delete conclusions | POST conclusions/list, POST conclusions/query, DELETE conclusions/{id} |
rebuilding; checks |
| O10 | search the workspace, with a result limit | POST search (rift: {"query", "limit"} and a bare array) |
recall |
| O11 | search a peer's messages and a session's messages, with a limit | POST peers/{p}/search, POST sessions/{s}/search |
recall, narrowed |
| O12 | the dialectic, non-streaming: query, target, session, reasoning level, whether to include evidence | POST peers/{p}/chat, POST chat |
ask_memory |
| O13 | a cheap probe that the server is up and speaks the expected version | whatever Honcho 3.2 offers; if nothing, a cheap read (O1's get) | memoryd's startup and health |
- R16 (MUST) O1 to O13 in the blocking API, with typed requests and responses. Fields the server may add later are ignored when decoding; fields Boxmaker relies on are required.
- R17 (SHOULD) O12 streaming, with the read timeout of R13 applying between chunks, so a caller can tell a slow answer from a dead one. The SSE must be parsed by lines across network chunks (see R20).
- R18 (MUST) One error type for the blocking client that lets the caller tell apart: cannot
connect, timed out (no bytes), HTTP status with the first 4 KiB of the body, response too large,
response not decodable, invalid configuration, and the workspace-delete conflict. Implements
std::error::ErrorandDisplay; never contains the API key.
3.4 Correctness against rift
- R19 (MUST) Every O-operation is exercised against rift 3.2.0 in a throwaway workspace (reads
may use
hermes-fleet, writes never), and the result recorded in a compatibility document in the repository: endpoint, method, request shape, response shape, the date, and the server's version. Where Honcho's docs and rift disagree, rift wins and the document says so. - R20 (MUST) Fix these divergences, found by reading the async client; they apply to the
blocking client too, and any fix to shared models or request code applies to both:
- Search shapes (O10, O11). rift takes
{"query", "limit"}at the top level and answers with a bare array (sift, above). roncho's workspace search sends{"query"}with no limit, and its peer and session searches send{"filters": {"query"}}; all three decode the answer as aPage. There is no way to set a limit. - Paging of messages (O5).
list_messagesbuilds thepage,sizeandreversequery and never sends it, so a paged read can keep getting the first page. - Message fields.
Message.roleis required when decoding; sift's verified field list has norole. Make every field Boxmaker does not need optional, and check which fields rift sends. - List methods. Peer and session lists use
GET, while Honcho 3's other lists (messages, conclusions, workspaces) arePOST …/list. Settle on what rift does, and the same forpeers/{p}/context. - Streaming. The SSE reader splits each network chunk into lines on its own, so a line split across two chunks is misparsed. Buffer until a newline.
- Build errors. A missing workspace is reported as
MissingApiKey, and a missing key is an error at all (R10). - Retries.
max_retriesis stored and never read (R13). - Workspace delete. The "active sessions" conflict is not told apart from other failures (O7, R18).
- Search shapes (O10, O11). rift takes
- R21 (MUST) Tests for the blocking client against a fake server in the test process
(
std::net::TcpListener), covering each O-operation's request bytes and response decoding, and also: a refused connection, a server that sends nothing (read timeout), a server that stops mid-body, a redirect (refused), an oversized body (capped), a non-JSON body, a 409 on workspace delete, and that noAuthorizationheader is sent without a key. Every wait in a test has a limit. - R22 (SHOULD) A live suite, ignored by default, run with an environment variable naming the server, that performs R19's checks and deletes its workspace at the end.
3.5 Semantics to find out and write down
Boxmaker's design depends on these answers. For each, check Honcho 3.2's documentation and rift, and record what you found (or "not found") in the compatibility document. Do not guess.
- Q1 Is authentication off on rift? If it can be on, are keys scoped per workspace, and is there a read-only key?
- Q2 Can a single message be deleted or edited in 3.2.0? What does deleting a session remove from conclusions, representations and summaries derived from it?
- Q3 The deriver: what triggers it (per message, per batch, per session), how long until conclusions appear under normal load, how to see whether a message has been processed, and which peer, session or workspace configuration keys turn derivation off for a peer.
- Q4 Message metadata: can search (O10, O11) and the dialectic (O12) be filtered by metadata (so that labelled messages can be excluded), and what is the filter grammar?
- Q5 What does a conclusion's
source_idsrefer to? Can a conclusion be traced to the messages it came from? - Q6 When does workspace deletion return 409 ("active sessions"), and how does a caller make a session inactive?
- Q7 Is there a health or version endpoint (O13)?
- Q8 Does the
3.2.0-pr1092image differ from upstream 3.2.0 or 3.2.1 in any endpoint above?
4. Out of scope for roncho
- Which turns are synced, trust per workspace, taint, sanitising text: Boxmaker's
memoryd. - Queues and progress tracking of a sync:
memorydkeeps its own. - A CLI. Boxmaker does not need one.
5. Done when
- A tagged release exists whose blocking feature meets R1 to R18, with the R20 fixes.
cargo tree -e normal --no-default-features -F blocking(with your feature name) shows no crate from R2's list, and every crate meets R5.- The compatibility document covers O1 to O13 and Q1 to Q8, dated, with rift's version.
- The fake-server tests of R21 pass with Rust 1.95; the live suite of R22 passes against rift.
- The owner is told the tag, and anything in this document that could not be done, and why.