How ai-memory Prevents Circular Dependencies Between Its Crates

ai-memory enforces acyclic crate dependencies through a strict hierarchical architecture where lower-level crates define core types and higher-level crates depend only downward, with cargo check automatically rejecting any cycle-inducing configuration.

Circular dependencies between crates break Rust builds, obscure module boundaries, and create maintenance nightmares. The ai-memory project, authored by Fabio Akita (akitaonrails/ai-memory), solves this with a layered workspace design that makes crate relationships explicit, unidirectional, and automatically verifiable.

Core-First Design: The Foundation of Acyclic Dependencies

The ai-memory-core crate sits at the bottom of the dependency hierarchy. It contains domain types, entity IDs, and pure logic data structures that every other crate consumes.

In crates/ai-memory-core/Cargo.toml:

[package]
name = "ai-memory-core"

This crate has no workspace dependencies. It exports primitives that upper layers import but never modify. This isolation prevents any upward dependency edge that could form a cycle.

One-Directional Dependency Flow

Higher-level crates depend strictly on lower-level ones. The hierarchy flows: Core → Store/LLM → Web.

Examine crates/ai-memory-store/Cargo.toml:

[package]
name = "ai-memory-store"

[dependencies]
ai-memory-core = { path = "../ai-memory-core" }   # Store depends on Core

The store crate consumes core types but does not expose itself to core. This pattern continues upward. In crates/ai-memory-web/Cargo.toml:

[package]
name = "ai-memory-web"

[dependencies]
ai-memory-store = { path = "../ai-memory-store" }   # Web depends on Store

ai-memory-llm   = { path = "../ai-memory-llm" }     # Web also depends on LLM

Notice that ai-memory-web accesses ai-memory-core types indirectly through ai-memory-store and ai-memory-llm. It does not declare a direct dependency on core, preserving the strict layering.

Workspace Enforcement via Cargo.toml

The root Cargo.toml declares all workspace members. Cargo's build system validates the dependency graph at resolution time—any cycle causes immediate failure with a clear error message.

This mechanical enforcement means:

  • Developers cannot accidentally introduce cycles through configuration
  • CI pipelines block cyclic dependency PRs automatically
  • Refactoring remains safe because cargo check validates the graph continuously

Documented Architectural Invariant

The "no circular deps" rule is codified in documentation, not just code. According to docs/ARCHITECTURE.md at line 349:

"No circular deps. Inter‑crate boundaries enforce the cross‑cutting invariants."

The same statement appears in AGENTS.md at line 194, ensuring all contributors understand this constraint before modifying crate relationships.

Why This Pattern Matters for Rust Workspaces

Rust's module system and Cargo's workspace design make circular dependencies particularly destructive:

  • Compilation order ambiguity — cycles prevent cargo from determining build phases
  • Incremental build invalidation — cyclic edges poison the dependency cache
  • Test isolation breakdown — integration tests cannot run against partial graphs

By contrast, ai-memory's DAG structure enables:

  • Parallel compilation of independent crate subtrees
  • Clean unit testing at each layer without mocking upstream crates
  • Predictable refactoring where changes to core propagate safely downward

Summary

  • Core-first layering: ai-memory-core defines primitives with zero workspace dependencies
  • Downward-only edges: Crates depend strictly on lower-level crates, never siblings or ancestors
  • Mechanical enforcement: Cargo's dependency resolver rejects non-DAG configurations automatically
  • Explicit documentation: docs/ARCHITECTURE.md and AGENTS.md state the invariant for contributors

Frequently Asked Questions

What happens if a developer tries to add a circular dependency?

cargo check or cargo build fails immediately with a resolution error. The workspace dependency graph must remain a directed acyclic graph, and Cargo's resolver enforces this at configuration time—no code compilation even begins.

Can crates at the same layer depend on each other?

No. The ai-memory architecture avoids sibling dependencies entirely. ai-memory-store and ai-memory-llm both depend on core but not on each other. This prevents diamond dependency problems and keeps the graph shallow.

Why not use a single crate instead of managing boundaries?

Multiple crates enforce physical module boundaries that single-crate codebases cannot guarantee. Teams can work in parallel without merge conflicts, compilation units stay small, and the dependency graph documents architectural intent explicitly.

How does this pattern scale as the workspace grows?

New crates insert at appropriate layers. A ai-memory-index crate would depend on ai-memory-core and be consumed by ai-memory-store. The existing hierarchy guides placement, and Cargo's validation ensures the graph stays acyclic regardless of workspace size.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →