RFC: Cycle Break for terraphim_config ↔ terraphim_persistence ↔ terraphim_multi_agent
Status: Draft (Phase 1 §7 stub). Decision: extract terraphim_persistence_traits. Alternative considered: terraphim_agent_contracts (rejected — see §Decision rationale).
1. Empirical edges (file-level)
Captured 2026-05-10 from crates/*/Cargo.toml:
| Edge | Source crate | Target crate | Manifest line |
|------|--------------|--------------|----------------|
| E1 | terraphim_config | terraphim_persistence | crates/terraphim_config/Cargo.toml (terraphim_persistence = { path = "../terraphim_persistence", version = "1.0.0" }) |
| E2 | terraphim_config | terraphim_multi_agent | crates/terraphim_config/Cargo.toml (terraphim_multi_agent = { path = "../terraphim_multi_agent" }) |
| E3 | terraphim_persistence | terraphim_config | crates/terraphim_persistence/Cargo.toml (terraphim_config = { path = "../terraphim_config" }) |
| E4 | terraphim_persistence | terraphim_multi_agent | crates/terraphim_persistence/Cargo.toml (terraphim_multi_agent = { path = "../terraphim_multi_agent" }) |
| E5 | terraphim_multi_agent | terraphim_config | crates/terraphim_multi_agent/Cargo.toml (terraphim_config = { path = "../terraphim_config", features = ["openrouter"] }) |
| E6 | terraphim_multi_agent | terraphim_persistence | crates/terraphim_multi_agent/Cargo.toml (terraphim_persistence = { path = "../terraphim_persistence" }) |
Six edges; all six pairs of the 3-clique are present. This is a fully-connected directed cycle, not a chain. Sentrux gate reports cycle_count = 2 at file-level — the file-level decomposition is finer than the crate-level 3-clique we see in manifests; both views are valid.
2. Concrete usage (file:line) — to be filled in Phase 2 §15
(Phase 1 stub does not enumerate every use site. Phase 2 specification interview produces the full list of cross-clique imports per file before the trait surface is finalised.)
Inputs needed for Phase 2:
rg "use terraphim_persistence" crates/terraphim_config/src crates/terraphim_multi_agent/srcrg "use terraphim_config" crates/terraphim_persistence/src crates/terraphim_multi_agent/srcrg "use terraphim_multi_agent" crates/terraphim_config/src crates/terraphim_persistence/src
3. Decision: extract terraphim_persistence_traits
A new crate crates/terraphim_persistence_traits/ holds:
trait PersistenceProvider— the dyn-safe interface persistence-consumers depend ontrait KeyValueStore— narrower KV interface for config-only callerstrait ConfigSource— the read side of config that multi_agent and persistence need without taking aterraphim_configdep- Associated error type(s) —
thiserror-derived - All async methods declared
#[async_trait]
Acyclic post-state:
terraphim_persistence_traits (new, leaf — only depends on terraphim_types, async-trait, thiserror)
↑ ↑ ↑
terraphim_config terraphim_multi_agent terraphim_persistence
(impl PersistenceProvider, KeyValueStore)terraphim_config no longer depends on terraphim_persistence or terraphim_multi_agent. terraphim_multi_agent no longer depends on terraphim_config or terraphim_persistence. Both depend only on the traits crate. terraphim_persistence keeps its real implementation but drops its terraphim_config and terraphim_multi_agent deps in favour of the traits.
4. Alternative considered: terraphim_agent_contracts (rejected)
A second abstraction crate exclusively for agent–config contracts. Rejected because:
- The empirical cycle is centered on persistence access, not agent-contract registration. Edges E1, E3, E4, E6 are all persistence-related; only E2 and E5 involve multi_agent.
- A
terraphim_agent_contractscrate would only break E2 + E5, leaving E1+E3 still cyclic via persistence ↔ config. - Adding two new abstraction crates instead of one increases surface area without proportional benefit.
If post-extraction the residual config↔multi_agent edges (which are NOT cycles by themselves once persistence is symmetric to traits) prove problematic, agent_contracts can be added later as Stage A.2 — the persistence_traits cut does not preclude it.
5. Verification
After the extract-and-rewire PR:
sentrux gate . # cycle_count must drop to 0 (or 1 if a residual file-level loop remains; investigate any non-zero)
cargo build --workspace # green
cargo public-api diff terraphim_config # only intentional removals (terraphim_persistence symbols)
cargo public-api diff terraphim_persistence # only intentional removals
cargo public-api diff terraphim_multi_agent # only intentional removalsPublic-API diffs SHOULD show removals of the moved trait-bound types from the consumer crates' surfaces. Anything other than the planned removals/additions is a regression.
6. Open questions for Phase 2.5 specification interview
- Are
PersistenceProvider,KeyValueStore,ConfigSourcethe right granularity, or should there be more (e.g.SecretsProvider)? - Do the existing trait method signatures need redesign for object-safety, or are they already
dyn-friendly? - Feature-flag matrix:
multi_agentcurrently usesterraphim_configwithfeatures = ["openrouter"]— does the trait need an OpenRouter-shaped extension trait? - Where does the traits crate live:
terraphim-corerepo (Phase 2 §13) or its ownterraphim-persistence-traitsrepo? — D5.
7. Out of scope for cycle-break
- Splitting
terraphim_persistenceitself into multiple backends (memory/sqlite/redb). The cycle break is orthogonal. - Renaming any of the three crates. Names stable.
- Changing the persistence file format or schema.