Skip to content

Route memory tool and query paths through the module seam so tinymemory-core leaves the build #5560

Description

@senamakel

Summary

The TinyMemory engine already runs as a TinyBus module — memory::binding states plainly that "the built-in driver is the compiled TinyMemory TinyBus module. The host no longer exposes an embedded engine class for memory." But tinymemory-core is still linked into the shipped binary, because the agent-tool and query paths call the crate's implementation directly instead of going through the MemoryProvider seam. Route those call sites through the provider so the crate can leave the build.

Problem / Context

The module extraction is half-applied, and the measurement makes the gap concrete. On 90dabbb5b, with the product feature set:

Module In the shipped build?
tinydocs no
tinyjuice no
tinyvoice no
tinymemory / tinymemory-core yes — still a path dependency
tinywallet yes (deliberate — see #5559)

tinymemory_core is 1.44 MB of .text, the 7th largest crate in the binary.

There are two live paths to memory. The driver binding goes over the bus, but 107 direct tinymemory_core:: references and 46 tinymemory_api:: references in src/ bypass it. They cluster in four places:

Call site Direct refs What it reaches for
memory/tools/ 9 files tree::retrieval, store::chunks
memory/query/ 8 files tree::retrieval, tree::score
memory/tree/ 6 files tree::tree, tree::health, tree::tree_runtime
memory/sync/ 6 files sync::composio, sync::sync_status

Plus chat::build_chat_runtime / build_chat_provider, source_scope::current_source_scope, tool_memory, thread_context.

This matters beyond binary size. A capability reachable by two different paths is one whose behaviour can diverge — the bus path enforces the error mapping in memory::api::wire, and ModuleMemoryProvider::verify cross-checks the module's declared capabilities against Capabilities::all. A direct crate call gets none of that. modules/memory.rs already documents why the error table is shared by both ends: reimplementing the mapping is "what would let a PathEscape arrive as an Invalid, silently reclassifying a sandbox escape as a caller mistake." Direct calls sidestep that reasoning entirely.

Context on why this is worth finishing rather than leaving: measured across the module extractions (127.1 MB → 116.9 MB), the binary's first-party share of .text rose from 73.4% to 75.7% and the monomorphization tax was identical at 16.7 MB. Modules are the only lever that removes dependency code from the build outright — documents went from 39 crates to none. Leaving tinymemory-core linked forfeits that for the largest domain in the tree.

Scope

In scope

  • Route memory/tools/, memory/query/, memory/tree/ and memory/sync/ through MemoryProvider (or add the bus methods they need if the trait does not cover them).
  • Decide the fate of tinymemory-api: it is the host-owned contract, so it plausibly stays. State the decision either way — the point is that it is deliberate.
  • Drop the tinymemory / tinymemory-core path dependencies once nothing names them, and remove the matching [package.metadata.cargo-machete] ignored entries.

Not in scope

  • tinywallet — tracked separately.
  • The memory-git gate. git2 / libgit2-sys / libz-sys are shed by that gate already and are a separate axis.

Implementation notes

  • ModuleMemoryProvider::new is synchronous and does no I/O by contract: memory::binding::build is called from CoreContext::memory_binding, which "roughly four thousand pre-boot tests invoke with no tokio runtime at all." Any new seam has to keep that property.
  • capabilities() is answered statically as Capabilities::all and cross-checked on first use. Widening the surface means keeping that cross-check honest.
  • The tinymemory and tinymemory-tinycortex deps exist for driver admission, not for calls — see the 2026-08-10 entry in scripts/kernel-floor.limits. Do not remove them as "unused" without checking admission still resolves an engine; cargo machete flags them precisely because they have no use.

Acceptance criteria

  • No direct engine callsgrep -r 'tinymemory_core::' src/ returns nothing outside modules/memory.rs and the binding.
  • Crate out of the buildcargo tree -e normal --no-default-features --features "$(bash scripts/ci/product-features.sh)" no longer lists tinymemory-core; verified with scripts/assert-shed.sh, not cargo tree -i.
  • Kernel floor ratcheted down — the shed is written back into scripts/kernel-floor.limits. An unratcheted improvement grows back unnoticed. Measure on Linux; macOS resolves +1/+1 from core-foundation / security-framework.
  • Behaviour unchanged — memory agent tools, recall/query/search and the sync pipelines pass through the bus path, with coverage that would fail if a call regressed to the crate.
  • Machete entries removed — the ignored list in the root Cargo.toml no longer names crates that are gone.
  • Docs updatedAGENTS.md's memory section reflects the single path.
  • Diff coverage ≥ 80% — the implementing PR meets the changed-lines coverage gate.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

priority: p2Soon. Real but survivable — a rough edge, a gap, a thing that will bite later.

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions