Skip to content

Commit 697ca6c

Browse files
authored
Merge pull request #74 from tinyhumansai/tinymemory-bus
Move the wire vocabulary into tinymemory-bus, beneath tinymemory-api
2 parents afaf853 + 8612196 commit 697ca6c

52 files changed

Lines changed: 1752 additions & 636 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 13 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ members = ["crates/*"]
2121
default-members = [
2222
"crates/tinymemory",
2323
"crates/tinymemory-api",
24+
"crates/tinymemory-bus",
2425
"crates/tinymemory-conformance",
2526
"crates/tinymemory-core",
2627
"crates/tinymemory-remote",

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +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
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
2634
├── tinymemory-core/ the substance: ingestion, the summary tree, chunk
2735
│ storage, entities, the graph, the diff ledger, goals,
2836
│ tool-memory, and the Composio sync layer. The largest

clippy.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,15 @@
22
# a code item that forgot its backticks. These are product and technology names
33
# written as prose on purpose; backticking them would imply they name a Rust
44
# item. `..` keeps clippy's own default list rather than replacing it.
5-
doc-valid-idents = ["..", "TinyMemory", "TinyCortex", "OpenHuman", "SQLite", "snake_case"]
5+
doc-valid-idents = [
6+
"..",
7+
"TinyMemory",
8+
"TinyCortex",
9+
"OpenHuman",
10+
"TinyBus",
11+
"SQLite",
12+
"snake_case",
13+
# Product names in `chunks::SourceKind`'s prose, not Rust items.
14+
"WhatsApp",
15+
"FastMail",
16+
]

crates/tinymemory-api/Cargo.toml

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,29 @@ license = "MIT"
1111
repository = "https://github.com/tinyhumansai/tinymemory"
1212
description = "Stable public contracts for the TinyMemory memory system"
1313

14-
# Deliberately dependency-light: this crate is the stable contract surface that
15-
# hosts compile against, so it must stay free of native, async-runtime, and
16-
# storage dependencies. Anything heavier belongs in the `tinycortex` engine
17-
# crate, never here.
14+
# Deliberately dependency-light: this crate is the driver contract an engine
15+
# compiles against, so it must stay free of native, async-runtime, and storage
16+
# dependencies. Anything heavier belongs in the `tinycortex` engine crate, never
17+
# here.
1818
#
19-
# The full set is intentionally small and pure-Rust. Beyond the
20-
# serde/error/async-trait baseline it carries exactly three additions, each
21-
# pulled in by a value type that has to keep behaving identically after the
22-
# move out of the engine crate:
19+
# The set shrank when the payload vocabulary moved to `tinymemory-bus`:
20+
# `chrono`, `sha2` and `uuid` went with the types that needed them
21+
# (`chunks::Metadata`, `chunks::chunk_id`, `ToolMemoryRule::generate_id`), and
22+
# `thiserror` went with `MemoryError`. What is left is what the *traits* and the
23+
# host seam need:
2324
#
24-
# - `chrono` — timestamps on chunk/tree nodes; the `serde` feature backs
25-
# `chunks::Metadata`'s `chrono::serde::ts_milliseconds`.
26-
# - `sha2` — the deterministic `chunks::chunk_id`.
27-
# - `uuid` — `tool_memory::ToolMemoryRule::generate_id` (v4 bytes, nibble
28-
# encoded). Only the `v4` feature is needed here; the engine
29-
# crate additionally enables `serde`.
30-
# - `schemars` — the `host::` config sections are still fields of the host's
31-
# root `Config`, which derives `JsonSchema` to generate the
32-
# settings schema the UI renders. Dropping the derive on the way
33-
# down here would silently shrink that schema. `schemars` is pure
34-
# Rust (serde + serde_json + dyn-clone + ref-cast) and carries
35-
# none of the forbidden dependencies below.
25+
# - `async-trait` — every capability-family trait is `async fn` on an
26+
# object-safe trait.
27+
# - `anyhow` — `traits::Memory` and the mandatory composition are
28+
# anyhow-typed.
29+
# - `schemars` the `host::` config sections are still fields of the host's
30+
# root `Config`, which derives `JsonSchema` to generate the
31+
# settings schema the UI renders. Dropping the derive on the
32+
# way down here would silently shrink that schema. `schemars`
33+
# is pure Rust (serde + serde_json + dyn-clone + ref-cast).
34+
# - `log` — the `host::cloud_providers` legacy-field migration logs what
35+
# it rewrote. The zero-dependency facade, not an
36+
# implementation.
3637
#
3738
# Nothing here may pull in `rusqlite`, `git2`, `reqwest`, `regex`, or an async
3839
# runtime. Guard with the FORWARD form, which is scoped to this package:
@@ -44,18 +45,17 @@ description = "Stable public contracts for the TinyMemory memory system"
4445
# scope and prints the whole-workspace inverse tree, so it exits 0 and looks
4546
# clean even when this crate is the one pulling the dependency in.
4647
[dependencies]
48+
# The wire vocabulary. Every payload type this crate exposes is defined there
49+
# and re-exported here, so a host that only makes calls into the loadable module
50+
# can depend on that crate alone and compile none of the traits, the null
51+
# driver, or the `host::` config surface. See `src/lib.rs`.
52+
tinymemory-bus = { path = "../tinymemory-bus" }
4753
anyhow = "1"
4854
async-trait = "0.1"
49-
chrono = { version = "0.4", features = ["serde"] }
50-
# `log` is the zero-dependency logging facade, not an implementation. The
51-
# `host::cloud_providers` legacy-field migration logs what it rewrote.
5255
log = "0.4"
5356
serde = { version = "1", features = ["derive"] }
5457
serde_json = "1"
5558
schemars = "1.2"
56-
sha2 = "0.11"
57-
thiserror = "2"
58-
uuid = { version = "1", features = ["v4"] }
5959

6060
[dev-dependencies]
6161
# The moved `host::` config sections are parsed from TOML in their own tests,

crates/tinymemory-api/src/host/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ mod embedding_host;
5252
mod embeddings;
5353
mod error_reporter;
5454
mod events;
55-
mod evidence;
5655
mod nlp;
5756
mod routes;
5857
mod usage;
@@ -72,7 +71,6 @@ pub use events::{
7271
EmbeddingHealthReason, MemoryEvent, MemoryEventSink, NoopEventSink, SyncTrigger,
7372
LOCAL_MODEL_UNAVAILABLE_KIND, MEMORY_USER_ERROR_SOURCE,
7473
};
75-
pub use evidence::EvidenceRef;
7674
pub use local_ai::{LocalAiConfig, LocalAiUsage};
7775
pub use nlp::{SpacyEntity, SpacyResponse};
7876
pub use routes::EmbeddingRouteConfig;
@@ -84,6 +82,7 @@ pub use storage_memory::{
8482
pub use subsystems::{
8583
MemoryDriverConfig, MemoryHooksConfig, MemorySubsystemConfig, SubsystemsConfig,
8684
};
85+
pub use tinymemory_bus::evidence::EvidenceRef;
8786
pub use usage::UsageInfo;
8887

8988
/// Effective default global memory-sync cadence (seconds) used when

crates/tinymemory-api/src/lib.rs

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,32 @@
11
//! Stable public contracts for the TinyMemory memory system.
22
//!
3-
//! This crate holds the value types, error enum, capability vocabulary, and
4-
//! storage trait that memory engines and their embedding hosts compile
5-
//! against. It is engine-neutral on purpose: `tinycortex` is the default
6-
//! embedded engine, not the owner of the contract, and a second engine
7-
//! (`supermemory`, `mem0`, a self-hosted HTTP backend) implements the same
8-
//! traits without either engine learning about the other.
9-
//! It is deliberately dependency-light (serde / serde_json /
10-
//! chrono / sha2 / anyhow / thiserror / async-trait / uuid only) so depending on
11-
//! the contract never drags in SQLite, git2, reqwest, regex, or an async
12-
//! runtime.
3+
//! This crate holds the traits a memory engine implements, the host seam it is
4+
//! bound through, and — re-exported from [`tinymemory_bus`] — the value types,
5+
//! error enum and capability vocabulary they exchange. It is engine-neutral on
6+
//! purpose: `tinycortex` is the default embedded engine, not the owner of the
7+
//! contract, and a second engine (`supermemory`, `mem0`, a self-hosted HTTP
8+
//! backend) implements the same traits without either engine learning about the
9+
//! other. It is deliberately dependency-light (serde / serde_json / anyhow /
10+
//! async-trait / schemars / log, plus `tinymemory-bus`) so depending on the
11+
//! contract never drags in SQLite, git2, reqwest, regex, or an async runtime.
12+
//!
13+
//! ## The vocabulary lives one layer down
14+
//!
15+
//! Every payload type is defined in [`tinymemory_bus`] and re-exported here at
16+
//! its historical path, so `tinymemory_api::types::MemoryEntry` is the *same
17+
//! item* as `tinymemory_bus::types::MemoryEntry`, not a structural twin.
18+
//!
19+
//! The split follows what a consumer actually needs. A **driver author**
20+
//! implements [`provider::MemoryProvider`] and wants this crate: traits, the
21+
//! null driver, the mandatory composition, the [`host`] seam. A **host** loads
22+
//! `tinymemory-module` over `TinyBus` and only makes calls — it names
23+
//! `MemoryEntry` and `MemoryCategory` and implements nothing — so it depends on
24+
//! `tinymemory-bus` alone and compiles none of this.
25+
//!
26+
//! Defining a second set of payload types for that host was the alternative,
27+
//! and it is the failure the root manifest's `[patch]` table exists to prevent:
28+
//! `MemoryCategory` from the module would not be `MemoryCategory` in the host,
29+
//! with a conversion at every call site that nothing type-checks.
1330
//!
1431
//! ## Self-contained by design
1532
//!
@@ -64,13 +81,26 @@
6481
//! round-trips [`error::MemoryError`] through. Shared by both ends of every
6582
//! such transport, so the names cannot drift apart.
6683
67-
pub mod capabilities;
68-
pub mod chunks;
6984
pub mod drivers;
70-
pub mod error;
71-
pub mod goals;
72-
pub mod health;
7385
pub mod host;
86+
87+
// The wire vocabulary, re-exported from `tinymemory-bus`.
88+
//
89+
// These modules used to be defined here. They moved down a layer because a
90+
// *host* needs them and needs nothing else in this crate: it loads
91+
// `tinymemory-module` and makes calls, so it names `MemoryEntry` and
92+
// `MemoryCategory` but implements no trait, binds no driver and parses no
93+
// config. Making it depend on the whole driver contract to spell a payload type
94+
// was the wrong shape.
95+
//
96+
// Re-exported rather than merely available, so every historical path still
97+
// resolves — `tinymemory_api::types::MemoryEntry` is the same item as
98+
// `tinymemory_bus::types::MemoryEntry`, not a twin of it. That identity is the
99+
// point: a second definition would need a conversion at the module seam that
100+
// nothing type-checks.
101+
pub use tinymemory_bus::{
102+
capabilities, chunks, error, goals, health, recall, tool_memory, tree, types, version, wire,
103+
};
74104
/// The mandatory-family composition: wrap any [`traits::Memory`] backend as a
75105
/// complete [`provider::MemoryProvider`].
76106
///
@@ -84,12 +114,6 @@ pub mod host;
84114
pub mod mandatory;
85115
pub mod null;
86116
pub mod provider;
87-
pub mod recall;
88-
pub mod tool_memory;
89117
pub mod traits;
90-
pub mod tree;
91-
pub mod types;
92-
pub mod version;
93-
pub mod wire;
94118

95-
pub use version::{is_compatible, CONTRACT_VERSION};
119+
pub use tinymemory_bus::{is_compatible, CONTRACT_VERSION};

crates/tinymemory-api/src/provider/chunks.rs

Lines changed: 6 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -31,90 +31,16 @@
3131
//! `docs/specs/2026-08-13-memory-module-port.md` §3.
3232
3333
use async_trait::async_trait;
34-
use serde::{Deserialize, Serialize};
3534

36-
use crate::chunks::{Chunk, SourceKind};
35+
use crate::chunks::Chunk;
3736
use crate::error::MemoryError;
3837
use crate::provider::types::SourceScope;
3938

40-
/// Filters for [`MemoryChunks::list_chunks`].
41-
///
42-
/// Every field is optional and they compose with AND. The default matches
43-
/// everything the scope allows, bounded by the driver's own safety cap.
44-
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
45-
pub struct ChunkQuery {
46-
/// Restrict to one source kind.
47-
#[serde(default)]
48-
pub source_kind: Option<SourceKind>,
49-
/// Restrict to one logical source id.
50-
#[serde(default)]
51-
pub source_id: Option<String>,
52-
/// Restrict to one owner.
53-
#[serde(default)]
54-
pub owner: Option<String>,
55-
/// Inclusive lower bound on source time, epoch milliseconds.
56-
#[serde(default)]
57-
pub since_ms: Option<i64>,
58-
/// Inclusive upper bound on source time, epoch milliseconds.
59-
#[serde(default)]
60-
pub until_ms: Option<i64>,
61-
/// Maximum rows. The driver clamps this to its own cap — a caller cannot
62-
/// raise the ceiling by asking for more.
63-
#[serde(default)]
64-
pub limit: Option<usize>,
65-
/// Rows to skip, for pagination.
66-
#[serde(default)]
67-
pub offset: Option<usize>,
68-
/// Drop chunks marked dropped by the lifecycle.
69-
#[serde(default)]
70-
pub exclude_dropped: bool,
71-
}
72-
73-
/// One chunk's stored embedding.
74-
///
75-
/// Returned as a list rather than a map because the wire form of a map keyed by
76-
/// chunk id is a JSON object, and an id is caller-supplied text; a list keeps
77-
/// the encoding independent of what an id happens to contain.
78-
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
79-
pub struct ChunkEmbedding {
80-
/// The chunk this vector belongs to.
81-
pub chunk_id: String,
82-
/// The vector, in the embedding space named by the requested signature.
83-
pub vector: Vec<f32>,
84-
}
85-
86-
/// One chunk plus the per-chunk facts stored beside it.
87-
///
88-
/// # Why a detail view rather than four accessors
89-
///
90-
/// An inspection caller wants the row, its body, where the body lives, its
91-
/// lifecycle state and whether it has been embedded. Exposing those as four
92-
/// methods would read naturally in-process and cost **four bus round trips per
93-
/// row** out of it — and this is used to render lists. One method, one trip.
94-
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
95-
pub struct ChunkDetail {
96-
/// The chunk row.
97-
pub chunk: Chunk,
98-
/// The chunk's body as stored in the content vault, when it could be read.
99-
///
100-
/// `None` means the vault read failed — distinct from an empty body, which
101-
/// is a legitimately empty chunk. A caller rendering a preview should fall
102-
/// back to [`Chunk::content`] rather than showing nothing.
103-
#[serde(default)]
104-
pub body: Option<String>,
105-
/// Path of the body in the content vault, when it has one.
106-
#[serde(default)]
107-
pub content_path: Option<String>,
108-
/// Lifecycle state (`active`, `dropped`, …); `None` when unrecorded.
109-
#[serde(default)]
110-
pub lifecycle_status: Option<String>,
111-
/// Whether an embedding vector exists for this chunk in **any** space.
112-
///
113-
/// Not scoped to a signature on purpose: this answers "has this been
114-
/// embedded at all", which is what an inspection view wants. Asking whether
115-
/// a *particular* space has it is [`MemoryChunks::chunk_embeddings`].
116-
pub has_embedding: bool,
117-
}
39+
// The value types this family exchanges. They are defined in `tinymemory-bus`
40+
// — they cross the module boundary, and a host that only makes calls must be
41+
// able to name them without compiling this trait — and re-exported here so
42+
// every historical path keeps resolving and the types stay the same types.
43+
pub use tinymemory_bus::provider::chunks::{ChunkDetail, ChunkEmbedding, ChunkQuery};
11844

11945
/// Direct read access to the chunk tier.
12046
///

0 commit comments

Comments
 (0)