Skip to content

Commit 8612196

Browse files
senamakelmedullabot
andcommitted
docs: clarify tinymemory-bus as the vocabulary crate beneath tinymemory-api
Rewrite both READMEs to reflect the architectural change that moved payload types from tinymemory-api down into tinymemory-bus. The bus crate is now the single source of truth for every type that crosses the module boundary, with tinymemory-api depending on it and re-exporting all of it. The old text described a crate that re-exported types from tinymemory-api; the new text describes a crate that owns them, with tinymemory-api as the consumer. The host-side call example is updated to show direct use of the bus crate's types and names rather than the old BusCall abstraction, and the section on why arguments get structs is removed since calls now use positional JSON directly. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
1 parent bf97eec commit 8612196

2 files changed

Lines changed: 108 additions & 124 deletions

File tree

README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,17 @@ crates/
2020
│ │ binds as, and the fail-closed external-driver gate
2121
│ ├── tests/ integration tests against the public API only
2222
│ └── examples/ runnable, compiled-in-CI usage examples
23-
├── tinymemory-api/ the contract. Dependency-light on purpose: depending on
24-
│ it never drags in SQLite, git2, reqwest, or an async
25-
│ runtime
26-
├── tinymemory-bus/ the wire contract for the loadable module: member names,
27-
│ the payload types, and one typed call per member. What a
28-
│ *host* links to talk to `tinymemory-module`, which ships
29-
│ as a `cdylib` and exports no Rust surface of its own
23+
├── tinymemory-api/ the driver contract: the traits an engine implements and
24+
│ the host seam it binds through, plus every
25+
│ `tinymemory-bus` type re-exported at its historical path.
26+
│ Dependency-light on purpose: depending on it never drags
27+
│ in SQLite, git2, reqwest, or an async runtime
28+
├── tinymemory-bus/ the wire vocabulary: every type that crosses the module
29+
│ boundary, plus the member names. Sits *below* the
30+
│ contract — `tinymemory-api` depends on it and re-exports
31+
│ it — so a host that only makes calls into
32+
│ `tinymemory-module` links this alone and compiles no
33+
│ traits, no null driver and no config surface
3034
├── tinymemory-core/ the substance: ingestion, the summary tree, chunk
3135
│ storage, entities, the graph, the diff ledger, goals,
3236
│ tool-memory, and the Composio sync layer. The largest

crates/tinymemory-bus/README.md

Lines changed: 97 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,97 @@
11
# tinymemory-bus
22

3-
The wire contract for the TinyMemory `TinyBus` module, as a library a host
4-
links.
5-
6-
TinyMemory ships as a loadable module so a host does not compile the engine.
7-
`crates/tinymemory-module` exports one object with 89 members on it, and it
8-
ships as a `cdylib` — a host can load it, but it cannot `use` anything out of
9-
it. This crate is what the host compiles against instead:
10-
11-
| module | what it holds |
12-
| -------- | -------------------------------------------------------------- |
13-
| `names` | the bus name, the object path, one constant per member |
14-
| `types` | every value type that crosses a frame |
15-
| `calls` | one struct per member: arguments in wire order, plus reply type |
16-
| `wire` | the error names, and the mapping back to `MemoryError` |
17-
18-
Four dependencies, none of them heavy: `tinymemory-api` for the types, `serde`
19-
and `serde_json` for the encoding, `thiserror` for one small error enum. No
20-
engine, no storage, no async runtime — and no `tinybus`.
21-
22-
## Why the types are re-exported, not defined
23-
24-
The obvious reading of "a crate that holds the bus types" is a crate that
25-
*defines* them. That would be a mistake, and the repository has already made
26-
the equivalent one once: when `tinymemory-api` was resolved twice, by git and
27-
by path, `MemoryCategory` from one copy was not the same type as
28-
`MemoryCategory` from the other, and the mismatch only surfaced at the seam.
29-
The root `Cargo.toml`'s `[patch]` table exists to prevent exactly that.
30-
31-
Defining structurally identical types here would reproduce it deliberately: the
32-
module would serve `tinymemory_api::` types, the host would hold
33-
`tinymemory_bus::` ones, and every call site would need a conversion whose
34-
correctness nothing checks. So there is one definition, in `tinymemory-api`,
35-
surfaced here. A host gets the types the module serves — the same types, not
36-
equivalents.
37-
38-
## Why not just depend on `tinymemory-api`
39-
40-
It would compile. But `tinymemory-api` is the **driver** contract: it also
41-
carries `MemoryProvider` and its eighteen capability traits, the
42-
mandatory-family composition, the null driver, and the `host::` config sections
43-
a host persists in `config.toml`. A host that loads the module implements none
44-
of that — it makes calls.
45-
46-
This crate is the subset that crosses a frame. What a host compiles against is
47-
what it can actually send and receive, and a trait method that is not exported
48-
on the bus is absent here rather than tempting.
49-
50-
## Why arguments get a struct
51-
52-
`#[tinybus::interface]` puts a method's arguments on the wire as a positional
53-
JSON array, decoded into a tuple on the far side. That is a fine encoding and a
54-
bad thing to write by hand. `Store` takes six arguments:
55-
56-
```json
57-
["work", "standup", "", "core", null, "internal"]
58-
```
59-
60-
Two are `Option`s, two are enums that serialize as strings, and swapping
61-
`namespace` with `key` produces a call that succeeds and writes the entry to the
62-
wrong place. Nothing on the module side can catch it — both are `String`, in
63-
the right position count, and the engine has no way to know which one the caller
64-
meant.
65-
66-
So a caller fills in named fields and `BusCall::into_args` does the positioning.
67-
The reply type travels with the call for the same reason: `Get` answers
68-
`Option<MemoryEntry>` and `Forget` answers `bool`, both are perfectly good JSON,
69-
and decoding one as the other fails somewhere far from the call.
70-
71-
## There is no client here
72-
73-
This crate holds no connection and no `call()` that sends anything. Two reasons.
74-
75-
A host already owns its connection — its reconnect policy, its timeouts, its
76-
tracing, its own idea of what a memory call costs it. A client here would either
77-
duplicate that or fight it, and the useful part is already in `calls` and
78-
`types`.
79-
80-
And structurally it could not work anyway: `tinybus` is a vendored submodule
81-
whose manifest inherits fields from its own nested `[workspace.package]`, so a
82-
member of this workspace that depends on it makes cargo resolve that inheritance
83-
against the wrong root and fail. That is why `crates/tinymemory-module` is its
84-
own workspace root — see the note on `exclude` in the root `Cargo.toml`. A
85-
contract crate a host links has no business being a separate workspace, so it
86-
stays transport-free.
87-
88-
Wiring it up host-side is small:
3+
Every type that crosses the TinyMemory `TinyBus` boundary, and the names of the
4+
members that carry them.
5+
6+
TinyMemory ships as a loadable module so a host does not compile the engine:
7+
`crates/tinymemory-module` exports one object with 89 members on it, built as a
8+
`cdylib`. A host can load that binary but cannot `use` anything out of it, so
9+
the payload vocabulary has to be published as an ordinary library. This is it.
10+
11+
| module | what it holds |
12+
| ---------------------------------------------------------------- | ---------------------------------------------- |
13+
| `names` | bus name, object path, one constant per member |
14+
| `types`, `chunks`, `recall`, `tree`, `goals`, `tool_memory`, `health`, `capabilities`, `evidence` | the value vocabulary |
15+
| `provider` | the value types each capability family exchanges |
16+
| `error`, `wire` | `MemoryError` and the name table it round-trips through |
17+
| `version` | `CONTRACT_VERSION` and the bind rule |
18+
19+
Seven dependencies, all pure Rust: `serde`, `serde_json`, `chrono`, `sha2`,
20+
`uuid`, `anyhow`, `thiserror`.
21+
22+
## This crate sits underneath `tinymemory-api`
23+
24+
`tinymemory-api` **depends on this crate and re-exports all of it**. That
25+
direction matters, and it is the opposite of the obvious one.
26+
27+
The payload types used to live in `tinymemory-api`. They moved down because a
28+
*host* needs them and needs nothing else in that crate: it loads the module and
29+
makes calls, so it names `MemoryEntry` and `MemoryCategory` but implements no
30+
trait, binds no driver and parses no config. Making it depend on the whole
31+
driver contract to spell a payload type was the wrong shape.
32+
33+
The alternative — a parallel set of payload types for hosts — is worse, and the
34+
repository has already had the equivalent bug: when `tinymemory-api` resolved
35+
twice, `MemoryCategory` from one copy was not the same type as `MemoryCategory`
36+
from the other, and the mismatch only surfaced at the seam. The root
37+
`Cargo.toml`'s `[patch]` table exists to stop that. One definition, here, at the
38+
bottom.
39+
40+
Because the re-export is by module rather than by item, every historical path
41+
keeps resolving unchanged — `tinymemory_api::types::MemoryEntry`,
42+
`tinymemory::MemoryCategory`, `tinycortex::memory::types::*` — and they are the
43+
same items, not twins.
44+
45+
So: a driver author depends on `tinymemory-api` and gets traits and vocabulary.
46+
A host depends on `tinymemory-bus` and gets vocabulary alone.
47+
48+
## What is deliberately absent
49+
50+
**No traits.** `MemoryProvider` and the eighteen capability-family traits
51+
describe what an engine must implement, not what a frame carries. They stay in
52+
`tinymemory-api`. The split is readable off the path: a name here is data, a
53+
name there is an obligation.
54+
55+
**No transport.** This crate does not depend on `tinybus` and holds no
56+
connection, client or codec. A host already owns its connection — its reconnect
57+
policy, its timeouts, its tracing — and the useful part is the vocabulary.
58+
59+
That is also structural, not just preference: `tinybus` is vendored as a
60+
submodule whose manifest inherits fields from its own nested
61+
`[workspace.package]`, so a member of this workspace that depends on it makes
62+
cargo resolve that inheritance against the wrong root and fail. It is why
63+
`crates/tinymemory-module` is its own workspace root — see the note on `exclude`
64+
in the root `Cargo.toml`. A crate every workspace member depends on has to stay
65+
transport-free.
66+
67+
**No host configuration, no null driver, no composition helpers.** Those are
68+
`tinymemory-api`'s, and none of them cross a frame.
69+
70+
## Making a call
71+
72+
Arguments travel as a positional JSON array — `#[tinybus::interface]` decodes
73+
them into a tuple — and the member name comes from `names`:
8974

9075
```rust,ignore
91-
use tinymemory_bus::calls::BusCall;
92-
use tinymemory_bus::names::{BUS_NAME, OBJECT_PATH};
93-
use tinymemory_bus::{types::MemoryError, wire};
94-
95-
/// Make one call, and give a failure back as the driver's own error type.
96-
async fn call<C: BusCall>(
97-
connection: &tinybus::Connection,
98-
call: C,
99-
) -> Result<C::Response, MemoryError> {
100-
let args = call
101-
.into_args()
102-
.map_err(|e| MemoryError::Invalid(e.to_string()))?;
103-
104-
match connection
105-
.call(BUS_NAME, OBJECT_PATH, C::METHOD, args)
106-
.await
107-
{
108-
Ok(body) => C::decode_response(body).map_err(|e| MemoryError::Other(e.into())),
109-
// The name is the contract; `from_wire` is the same table the module
110-
// mapped out through, so the variant survives the round trip.
111-
Err(tinybus::Error::MethodFailed { name, message }) => {
112-
Err(wire::from_wire(&name, &message))
113-
}
114-
Err(other) => Err(MemoryError::Other(other.into())),
76+
use tinymemory_bus::names::{methods, BUS_NAME, OBJECT_PATH};
77+
use tinymemory_bus::types::MemoryEntry;
78+
use tinymemory_bus::wire;
79+
80+
let body = serde_json::json!([namespace, key]);
81+
match connection.call(BUS_NAME, OBJECT_PATH, methods::GET, body).await {
82+
Ok(reply) => Ok(serde_json::from_value::<Option<MemoryEntry>>(reply)?),
83+
// The name is the contract, and `from_wire` is the same table the module
84+
// mapped out through, so the variant survives the round trip.
85+
Err(tinybus::Error::MethodFailed { name, message }) => {
86+
Err(wire::from_wire(&name, &message))
11587
}
88+
Err(other) => Err(other.into()),
11689
}
11790
```
11891

119-
`OpenStore` is the one member that needs more than that: it returns an object
120-
*path*, not a value, and calls against that path use the same `BUS_NAME` and the
121-
same member names. Treat `OBJECT_PATH` as the root object rather than the only
122-
one.
92+
`OpenStore` is the one member that returns an object *path* rather than a value:
93+
a sibling store under the same workspace, exporting the identical interface.
94+
Treat `OBJECT_PATH` as the root object, not the only one.
12395

12496
## Staying in step with the module
12597

@@ -130,6 +102,14 @@ two — this crate lists members by hand, the module derives them from its
130102
`#[tinybus::interface]` block — so that test is what turns a drift into a
131103
`cargo test` failure instead of an `UnknownMethod` in a host at runtime.
132104

133-
Adding a member is therefore three edits in this crate: a constant in
134-
`names::methods`, an entry in `names::METHODS`, and a call struct in the
135-
matching `calls` family (which `calls::test::COVERED` also lists).
105+
Adding a member is two edits here: a constant in `names::methods` and an entry
106+
in `names::METHODS`.
107+
108+
## Lints
109+
110+
`clippy::pedantic` is deliberately off, matching `tinymemory-tinycortex` and
111+
`tinymemory-remote`. These modules arrived verbatim from `tinymemory-api`, which
112+
opts into no lints at all; switching pedantic on over the move would have buried
113+
a mechanical relocation under several hundred unrelated `#[must_use]` and
114+
backtick edits. Turning it on is worth doing as its own change, over
115+
`tinymemory-api` too, so the contract and the vocabulary stay lint-compatible.

0 commit comments

Comments
 (0)