M5b: memoryd reaches Honcho through roncho; what roncho must provide
The owner chose roncho over a client of memoryd's own. The requirements for its developer: a blocking client whose dependency graph fits Boxmaker's rules (no async runtime, licences, no duplicate versions, Rust 1.95), no default server and no hidden environment settings or redirects, caller-set idle timeouts and body caps, thirteen operations checked against rift, the search and paging fixes found reading the code, and eight questions about Honcho 3.2 to answer from its docs and rift. References are to roncho at f24fc4a. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ Newest first. A decision that changes `docs/design.md` lands in the same commit
|
|||||||
|
|
||||||
| Date | Decision | Reason |
|
| Date | Decision | Reason |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
| 2026-09-25 | `memoryd` reaches Honcho through the owner's `roncho`, not a client of its own. `roncho` first gains a blocking client with a small dependency graph (no async runtime), no default server, no hidden environment settings or redirects, and request shapes checked against rift; the requirements are `docs/specs/2026-09-25-roncho-requirements.md`, handed to `roncho`'s developer. Boxmaker will allow that one git source in `deny.toml`, pinned by tag. | Owner's decision (M5b design). One client for the fleet. As it stands `roncho` is async only (tokio, reqwest), defaults to `api.honcho.dev`, and differs from rift in its search shapes, so it cannot be used unchanged. |
|
||||||
| 2026-09-24 | M5a's check on memory writes stays short and fixed: control characters, invisible characters, chat-template markup, six phrases. A research thread on prompt-injection defences feeds a sanitizer module for memory writes (and possibly other persisted text), built as hardening in M5b. Memory writes are not posted to the chat; `bxctl memory log` lists them. | Owner. The archive routing of untrusted sessions' writes is the main defence; a pattern list is defence in depth and should rest on research, not guesses. |
|
| 2026-09-24 | M5a's check on memory writes stays short and fixed: control characters, invisible characters, chat-template markup, six phrases. A research thread on prompt-injection defences feeds a sanitizer module for memory writes (and possibly other persisted text), built as hardening in M5b. Memory writes are not posted to the chat; `bxctl memory log` lists them. | Owner. The archive routing of untrusted sessions' writes is the main defence; a pattern list is defence in depth and should rest on research, not guesses. |
|
||||||
| 2026-09-24 | Memory writes need no approval, as in Hermes (the owner's `~/.hermes` has no `write_approval`). What a session that has seen untrusted content wants remembered becomes a note, labelled untrusted, not identity in the prompt (refined the same day: identity and experience are separate kinds of memory). Every write is scanned, journalled (listed by `bxctl memory log`, not posted anywhere) and can be undone; nothing is deleted. Brief change P21 proposed; the M5a spec is revised to match. Session-scoped approvals for other tools become an open decision. | Owner: an agent harness should act without a stream of questions, and reading and writing memory is what an agent is. The only risk the approval guarded is injected text reaching every future prompt, and that needs a session that read untrusted content; `brokerd` knows which sessions those are. |
|
| 2026-09-24 | Memory writes need no approval, as in Hermes (the owner's `~/.hermes` has no `write_approval`). What a session that has seen untrusted content wants remembered becomes a note, labelled untrusted, not identity in the prompt (refined the same day: identity and experience are separate kinds of memory). Every write is scanned, journalled (listed by `bxctl memory log`, not posted anywhere) and can be undone; nothing is deleted. Brief change P21 proposed; the M5a spec is revised to match. Session-scoped approvals for other tools become an open decision. | Owner: an agent harness should act without a stream of questions, and reading and writing memory is what an agent is. The only risk the approval guarded is injected text reaching every future prompt, and that needs a session that read untrusted content; `brokerd` knows which sessions those are. |
|
||||||
| 2026-09-24 | M5a spec review: the policy of `recall` (`auto`) and `memory` (`ask`) is built into `brokerd`, and grant files cannot name them; tool calls are indexed by name and arguments, tool results are not; summaries of tool output, where a tool opts in, come with M5c's summariser. | Owner: an agent that cannot write memories is no better than a plain chat window, and a grant file must not be able to turn edits off or make them unattended. A web fetch's summary is worth keeping; most tools' output is not. |
|
| 2026-09-24 | M5a spec review: the policy of `recall` (`auto`) and `memory` (`ask`) is built into `brokerd`, and grant files cannot name them; tool calls are indexed by name and arguments, tool results are not; summaries of tool output, where a tool opts in, come with M5c's summariser. | Owner: an agent that cannot write memories is no better than a plain chat window, and a grant file must not be able to turn edits off or make them unattended. A web fetch's summary is worth keeping; most tools' output is not. |
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
# 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. Code
|
||||||
|
references are to `roncho` at `f24fc4a` ("Initial commit").
|
||||||
|
|
||||||
|
## 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 `memoryd` is 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 a `Page` (`~/src/sift/internal/fleetctx/fleetctx.go:115-147`,
|
||||||
|
`~/src/sift/docs/P2.md:103-105`).
|
||||||
|
- 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:105`; `sift/docs/PLAN.md:38` |
|
||||||
|
| 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:117-127` |
|
||||||
|
| 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:236-237` |
|
||||||
|
| Workspaces in use | `hermes-fleet`, `guardian-star` (other agents'); each Boxmaker will have its own | `imladris/hw/orion/default.nix:106-107` |
|
||||||
|
|
||||||
|
**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 of `tokio`, `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 on `std::net` or 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 and
|
||||||
|
`Content-Length` bodies.
|
||||||
|
- **R4 (SHOULD)** `https://` behind an optional feature, using `rustls` 0.23 with the `ring`
|
||||||
|
provider and the platform's certificates (`rustls-native-certs` 0.8). Not `webpki-roots`,
|
||||||
|
not `aws-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:
|
||||||
|
`serde` 1.0.229, `serde_json` 1.0.151, `rustls` 0.23.45, `ring` 0.17.14,
|
||||||
|
`rustls-native-certs` 0.8.4, `thiserror` 2.0.21, `syn` 3, `toml` 1.1.6, `zeroize` 1.9.0.
|
||||||
|
Boxmaker's `cargo deny` runs with `multiple-versions = "deny"`.
|
||||||
|
- **R6 (SHOULD)** Keep the blocking graph small: beyond `serde` and `serde_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 over `chrono`, `uuid` and `url` in the blocking models. If you keep any of
|
||||||
|
them, use `default-features = false` (for `chrono`, no `clock`, which pulls platform time-zone
|
||||||
|
crates) and say why in the README. `thiserror` is acceptable: Boxmaker already locks it.
|
||||||
|
- **R7 (MUST)** Builds and passes its tests with Rust 1.95 (Boxmaker's `rust-version`). Set
|
||||||
|
`rust-version` in `Cargo.toml`.
|
||||||
|
- **R8 (MUST)** A git tag per release that Boxmaker can pin (`rev` or `tag`), and a changelog
|
||||||
|
entry saying what changed in the blocking API. Boxmaker will add the git source to its
|
||||||
|
`deny.toml` allow-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, no `Authorization` header at all. Building without a key is not an error. The key never
|
||||||
|
appears in an error, a `Debug` output 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_*` (today `build()` falls back to `HONCHO_WORKSPACE_ID` and
|
||||||
|
`HONCHO_API_KEY`, `src/client.rs:244-253`), not proxy settings (`HTTP_PROXY`, `ALL_PROXY` and 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_retries` for 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 | not in roncho at `f24fc4a`; find it in Honcho 3.2's docs | 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, if Honcho has one | not in roncho at `f24fc4a` | 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::Error` and `Display`; 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, found reading the current code, in the async client too:
|
||||||
|
- search request and response shapes (O10, O11): rift takes `{"query", "limit"}` at the top
|
||||||
|
level and returns a bare array (sift, above). roncho's workspace search sends `{"query"}` with
|
||||||
|
no limit and expects a `Page` (`src/api/workspace.rs:13-19`); peer and session search send
|
||||||
|
`{"filters": {"query"}}` and expect a `Page` (`src/api/peers.rs:174-184`,
|
||||||
|
`src/api/sessions.rs:178-186`).
|
||||||
|
- `list_messages` computes the `page`, `size` and `reverse` query but never sends it
|
||||||
|
(`src/api/sessions.rs:110-120`), so a paged read can loop on the first page.
|
||||||
|
- `Message.role` is required when decoding (`src/models/message.rs:5-16`); sift's verified field
|
||||||
|
list has no `role`.
|
||||||
|
- peer and session lists use `GET` (`src/api/peers.rs:23-28`, `src/api/sessions.rs:18-23`);
|
||||||
|
Honcho 3's other lists are `POST …/list`. Settle on what rift does, and the same for
|
||||||
|
`peers/{p}/context` (`src/resources/peer.rs:136-141`).
|
||||||
|
- the SSE reader splits each network chunk into lines on its own, so a line split across two
|
||||||
|
chunks is misparsed (`src/api/peers.rs:122-135`, `src/api/workspace.rs:107-120`).
|
||||||
|
- `build()` reports a missing workspace as `MissingApiKey` (`src/client.rs:244-248`).
|
||||||
|
- `max_retries` is stored and never read (`src/client.rs:20`, `264`; R13).
|
||||||
|
- **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 no `Authorization` header 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_ids` refer 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-pr1092` image 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: `memoryd` keeps its own.
|
||||||
|
- A CLI. `roncho` has none at `f24fc4a`, and 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.
|
||||||
Reference in New Issue
Block a user