car-topology 0.55.0

Amortized coordination-topology selection core for Common Agent Runtime
Documentation
//! Amortized coordination-topology selection for CAR.
//!
//! Implements the design of **"Codebook Agent: Amortized Topology Design for
//! LLM Multi-Agent Systems"** ([arXiv:2609.02264]) as a deterministic,
//! dependency-light core: pick which communication topology a multi-agent run
//! should use, from a short list, in one feed-forward pass, scored on
//! **measured** utility and **measured** tokens.
//!
//! [arXiv:2609.02264]: https://arxiv.org/abs/2609.02264
//!
//! # Why CAR wants this
//!
//! CAR already decides how to coordinate agents. `car_agents::coordinator::
//! Coordinator` spends a full LLM round trip on a hand-written prompt to pick
//! among four patterns, and nothing feeds back: a pattern that solved the task
//! for 3× the tokens is indistinguishable, to the next call, from one that
//! solved it cheaply. `car-multi` executes whichever pattern the caller names.
//!
//! This crate is the missing half — a selector fitted on what coordination runs
//! actually cost and actually achieved. It replaces an LLM call with a fold
//! over records CAR can already produce ([`TokenAccounting`] rides on every
//! `AgentOutput`), and it improves as records accumulate rather than as someone
//! rewrites a prompt.
//!
//! # The three measurements the design rests on
//!
//! The paper's argument is empirical, and all three claims are properties of a
//! *workload*, not theorems. [`diagnostics`] re-measures each on your own
//! records, and you should run it before trusting anything else here:
//!
//! | Claim | What the paper measured | Your check |
//! |---|---|---|
//! | The useful design space is a short list | Reward-surviving topologies collapse to ~6 distinct graphs even as codebook capacity grows 8 → 64 | [`diagnostics::design_space_collapse`] |
//! | The structural cost surrogate is inverted | Edge count correlates with measured tokens at `r ≈ -0.4`, so minimizing `\|E\|` *raises* the bill | [`diagnostics::edge_count_token_correlation`] |
//! | Message-passing scorers are topology-blind on homogeneous teams | With identical agent profiles every aggregator pools identical features, so every candidate scores the same | [`diagnostics::team_homogeneity`] |
//!
//! [`diagnose`] folds the first two into one [`DiagnosticsReport`] —
//! [`DiagnosticsReport::render`] for a terminal, [`DiagnosticsReport::headline`]
//! for a log line — and reports a [`Sufficiency`] alongside them, because a
//! sample below the paper's own collection protocol should not read like a
//! finding. The third claim needs the team's agent profiles, which an
//! [`ExecutionRecord`] does not carry, so it reads as unmeasured unless you call
//! [`diagnose_with_profiles`].
//!
//! The third is the one CAR should care about most, because CAR's teams are
//! usually homogeneous — four instances of the same agent spec, differing only
//! in the task text they are handed. On such a team a profile-node graph
//! network is not a weak ranker; it is a constant.
//!
//! # The pipeline
//!
//! ```text
//!   offline, once            fitted, no model calls          per query
//!  ┌────────────────┐      ┌──────────────────────┐      ┌──────────────┐
//!  │ execute fixed  │      │ Codebook   (short    │      │ predictor →  │
//!  │ topologies,    │ ───► │            list)     │ ───► │ top-M codes  │
//!  │ log (A,c,u,τ)  │      │ CodePredictor (prior)│      │ → decode     │
//!  └────────────────┘      │ ExecutionProxy       │      │ → 1 batched  │
//!                          │            (û, ĉ)    │      │   proxy call │
//!                          └──────────────────────┘      │ → argmax     │
//!                                                        └──────────────┘
//! ```
//!
//! ```
//! use car_topology::{
//!     CoordinationShape, ExecutionRecord, RecordSet, SelectorConfig, Topology,
//!     TopologySelector,
//! };
//!
//! # fn main() -> Result<(), car_topology::TopologyError> {
//! // 1. Offline: execute each training task under the six fixed families and
//! //    log what each run cost and achieved.
//! let mut records = Vec::new();
//! for task in 0..8 {
//!     let query = vec![task as f32 / 8.0, 1.0 - task as f32 / 8.0];
//!     for topology in Topology::collection_protocol(4)? {
//!         // In production these come from a real run; here, a stand-in.
//!         let tokens = 800 + 400 * topology.edge_count() as u64;
//!         records.push(ExecutionRecord::new(
//!             format!("task{task}"), query.clone(), topology, 1.0, tokens,
//!         ));
//!     }
//! }
//!
//! // 2. Fit. No LLM calls from here on.
//! let selector = TopologySelector::fit(
//!     &RecordSet::new(records)?,
//!     &SelectorConfig::default(),
//! )?;
//!
//! // 3. Per query: one pass, no search.
//! let selection = selector.select(&[0.5, 0.5])?;
//! assert!(selection.objective.is_finite());
//! # Ok(())
//! # }
//! ```
//!
//! # What this implementation is not
//!
//! The paper's three learned stages are a VQ autoencoder, an MLP prior, and an
//! MLP proxy with an auxiliary structure-token head. CAR has no autograd
//! framework in the workspace, and this crate is a deterministic leaf that must
//! stay one, so each stage is fitted in closed form instead:
//!
//! | Paper | Here | Consequence |
//! |---|---|---|
//! | VQ-VAE over `vec(A)`, EMA codebook, dead-code reset | Deterministic k-medoids over Hamming distance ([`Codebook`]) | Codes are topologies that were really executed; the fit is reproducible; it cannot emit a graph *between* two observed ones |
//! | MLP `p(k\|c)` under soft cross-entropy (Eq. 7) | Cosine-similarity kernel regression on the same reward-weighted soft targets ([`CodePredictor`]) | Same inputs, targets, and outputs; no generalization to query directions far from every training condition |
//! | MLP `[vec(A); c] → (û, ĉ)` (Eqs. 8–9) | Locally-weighted closed-form ridge on the same targets ([`ExecutionProxy`]) | Query conditioning moves from the features into the record weighting; still linear in the edges, so an edge-*pair* interaction is out of reach, and the fit keeps its training records rather than being a fixed-size model |
//! | Structure-token auxiliary loss (Eq. 10) | Dropped | It regularizes a shared hidden representation; ridge has none. The ridge penalty is the weaker substitute |
//!
//! Eq. (2) (per-task token normalization), Eq. (6) (reward-weighted soft
//! targets), and Eq. (11) (the one-pass selection rule) are reproduced exactly.
//!
//! **The paper's benchmark numbers are the paper's.** Its 84.6 average, 2.4 ms
//! generation, and 21.9–33.2% token reduction were measured on GSM8K / MATH /
//! MultiArith / SVAMP / MBPP / HumanEval with gpt-4o-mini and Qwen-3-8B, under
//! its own implementation. Nothing here has been evaluated against those
//! benchmarks, and the substitutions above mean this implementation should not
//! be assumed to reach them. What transfers without an experiment is the
//! *reasoning*: the three measurements, which [`diagnostics`] lets you make on
//! your own data.
//!
//! # Boundaries
//!
//! No model calls, no network, no clock, and no randomness that is not seeded.
//! Fitting and selection are pure functions of their inputs, so every stage
//! above is testable without a daemon, a model, or a filesystem.
//!
//! [`journal`] is the one module that touches disk — an append-only JSONL log
//! of execution records, kept separate for exactly that reason. It also carries
//! the guard that makes persisted records safe to reuse: every line names the
//! encoder that produced its query embedding, and a set can never be folded
//! across two of them. The pipeline compares queries by cosine similarity, so
//! mixing embedding spaces does not fail or look wrong — it answers from
//! arbitrary similarities.
//!
//! Record collection — the one step that costs real LLM calls — remains the
//! caller's, and [`Topology::collection_protocol`] is the protocol to collect
//! under.
//!
//! [`TokenAccounting`]: https://docs.rs/car-multi

pub mod codebook;
pub mod diagnostics;
pub mod error;
pub mod journal;
pub mod predictor;
pub mod proxy;
pub mod record;
pub mod select;
pub mod topology;

pub use codebook::{Codebook, CodebookConfig};
pub use diagnostics::{
    design_space_collapse, diagnose, diagnose_with_profiles, edge_count_token_correlation,
    scorer_advice, team_homogeneity, CapacityPoint, CollapseReport, CostSurrogateVerdict,
    DiagnosticsReport, Homogeneity, ScorerAdvice, Sufficiency, DEFAULT_CAPACITY_SWEEP,
    PROTOCOL_RECORDS, PROTOCOL_TASKS,
};
pub use error::TopologyError;
pub use journal::{
    journal_path, JournalEntry, JournalError, RecordJournal, JOURNAL_FILE, JOURNAL_SUBDIR,
    SCHEMA_VERSION,
};
pub use predictor::{CodePredictor, PredictorConfig};
pub use proxy::{ConditionedProxy, ExecutionProxy, ProxyConditioning, ProxyConfig, ProxyScore};
pub use record::{ExecutionRecord, RecordSet, DEFAULT_COST_WEIGHT, DEFAULT_SURVIVOR_THRESHOLD};
pub use select::{Candidate, Selection, SelectorConfig, TopologySelector};
pub use topology::{shape_of, CoordinationShape, Topology};