The engine-neutral memory layer for TinyHumans agents.
A host that embeds TinyMemory performs every memory operation through one contract, and picks which engine answers it by configuration rather than by recompiling. TinyCortex is the default embedded engine; a second engine implements the same traits and binds in its place without the host learning anything new.
api/ tinymemory-api — the contract. Dependency-light on
purpose: depending on it never drags in SQLite, git2,
reqwest, or an async runtime.
src/
├── lib.rs re-exports the contract wholesale, so a host takes one
│ dependency and the types are the same types
├── registry/ driver admission — which ids exist, what class each
│ binds as, and the fail-closed external-driver gate
└── mandatory/ the three mandatory capability families, composed once
over the `Memory` storage trait
adapters/
└── tinycortex/ the TinyCortex engine seen through the contract
vendor/
├── tinycortex/ the engine, pinned as a submodule
└── tinybus/ pinned TinyBus submodule
MemoryProvider is an object-safe trait with three mandatory capability
families and ten optional ones. The mandatory three are supertraits, so a
driver missing any of them cannot be constructed; the optional ten are reached
through as_ingest() / as_tree() / … accessors that default to None, so a
minimal driver implements what it supports and inherits correct absence for
everything else.
A driver's advertised set and its reachable accessors must agree.
audit_provider checks exactly that, which turns "advertised but not
implemented" into a detectable, testable mistake rather than a runtime surprise
on the first call.
Capabilities are asked once, at bind time, and cached: a host filters its RPC surface and its agent-tool list from the answer, so a set that changed afterwards would not be noticed.
| Here | In the host |
|---|---|
| the contract; capability negotiation; driver admission; the shared mandatory families; per-engine adapters | RPC surface, agent tools, security policy, credentials, schedulers, event bus, config mapping |
Policy is not here, on purpose. Tier enforcement, scope predicates, taint stamping, redaction, egress checks and audit belong in a decorator the host owns, on the path every caller takes. A driver that could be swapped for one that skips enforcement is the entire reason the policy layer exists.
- Implement
tinymemory_api::traits::Memoryfor the backend, overridingstore_with_taint— the trait default silently drops the taint, which would launder externally-sourced content into internal-trust content. - Wrap it:
MemoryTraitProvider::new(backend, "my-engine"). That yields a driver advertising Core, Recall and Portability, with the four easy-to-get-wrong parts (seesrc/mandatory/mod.rs) already handled. - Implement any optional families over the engine's own entry points, and
widen
capabilities()in lockstep with the accessors. - Reserve the driver id:
DriverRegistry::builtin().with_reserved("my-engine", DriverClass::Embedded).
git submodule update --init --recursive
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all -- --checkEngine adapters name their engines by version requirement, not path, so a
host that already pins its own engine checkout unifies onto one copy through its
own [patch.crates-io]. The workspace root patches them to the nested vendor/
submodules for a standalone build. A path dependency in an adapter would defeat
that and hand a host two copies of one engine with two incompatible Memory
traits.