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