Referencereference/parity

Parity & Lineage

The Rust edition is a clean-room, 100% Rust rebuild of the indusagi framework, ported module-for-module from the TypeScript ground truth (indus-rebuild/src/) into one Cargo workspace that compiles to a single static binary. It reuses no third-party application source; every wire shape is re-derived from public HTTP docs and parsed by hand (zero provider SDKs). The rebuild ran milestone-by-milestone (M0…M10) to a declared 26/26 full parity verdict with the TS and Python editions, trading the language wire-up for native-binary footprint and start-up latency (~86× cold start, ~30× peak RSS, ~27× install footprint vs Node).

Table of Contents

Summary

The Rust rebuild is an independent implementation, designed and written from scratch. As CREDITS.md and NOTICE put it: it is

an independent, 100%-Rust implementation, designed and written from scratch. It reuses no third-party application source code. It is the Rust rebuild of the clean-room TypeScript framework (npm indusagi); the Rust line continues that version line, starting at 0.13.1.

Parity is tracked against a 26-item milestone checklist walked against the merged crates/indusagi/src/ tree. All rows resolve to done, with eight deliberate waivers (decisions, not gaps) and a small set of drift gates that keep the clean-room invariants enforced by tests.

count
Checklist items 26
Done 26
Partial 0
Missing 0

Verdict: full parity achieved (26/26). The last item to close was TUI theme-live runtime switching, which is now wired end-to-end and Rust-tested (P-1 closed). The headline build gate is green: cargo build/test --workspace runs 2062 tests passing / 0 failing (the suite run twice with no flakes).

This is the same tree the rest of these docs cover area by area; see the Architecture overview for the module map it audits.

Lineage and license posture

The Rust edition is licensed MIT (Copyright (c) 2026 Varun Israni). It implements the relevant open standards directly from their primary sources rather than wrapping any existing application code:

Standard Source
OAuth 2.0 PKCE RFC 7636
Server-Sent Events framing WHATWG HTML / EventSource spec
Model Context Protocol modelcontextprotocol.io spec + SDK
JSON Schema json-schema.org
VT/ANSI/Kitty terminal escape grammars vendor terminal specs
Provider HTTP request/response field names each provider's public API docs

Third-party Rust crates (not application source) are used under their own permissive licenses (MIT / Apache-2.0 / BSD / ISC / Zlib / Unicode). The shipped binary statically links them; the full per-crate license bundle is generated by cargo about and shipped as THIRDPARTY.md in every release tarball, the dependency SBOM is embedded via cargo-auditable (reproducible against the committed Cargo.lock), and the cargo-deny license gate (deny.toml) enforces the allowlist over the whole dependency tree. The load-bearing crates the binary builds on:

Crate(s) Role
tokio / tokio-util / tokio-stream / futures Async runtime and streaming primitives
serde / serde_json (preserve_order) Serialization + canonical-JSON field ordering
reqwest (rustls TLS, no OpenSSL) HTTP/streaming transport for connectors + web tools
rmcp Rust Model Context Protocol client/host (feature mcp)
ratatui / crossterm Terminal UI surface (feature tui)
regex, indexmap, sha2, hex, ulid, thiserror, anyhow, parking_lot, unicode-width, unicode-segmentation Core utilities across subsystems
include_dir Compile-time embedding of the smithy knowledge pack

The lineage posture is enforced, not just declared: cargo run -p xtask -- lineage re-derives source-hygiene markers and exits 0, and runs as a release gate (see Release gates).

The milestone roadmap

The rebuild was executed milestone-by-milestone, M0 through M10 (11 milestones). Each milestone delivered a coherent slice of the framework, scaffolded then fanned out per module file, then verified against a green cargo build/test before moving on. The original plan split the framework into one library crate per subsystem; at the packaging milestone the thirteen indusagi-* library crates were merged into the single indusagi crate as modules (npm-style one package), so the shipped workspace has three crates plus the xtask build tool — see the architecture page.

Milestone Delivered Lands in module(s)
M0 Workspace, toolchain pin (1.96.0, edition 2024), CI/lineage scaffolding Cargo.toml, rust-toolchain.toml, xtask
M1 Cross-cutting primitives; zero-SDK LLM gateway; OTel-free tracer core, llmgateway, tracing
M2 Pure-FSM agent loop + content-addressed session DAG; the 12 built-in tools runtime, capabilities
M3 MCP client + provider host; Composio SaaS bridge interop, connectors_saas
M4 File-backed multi-agent crews; the IndusForge agent-builder swarm, smithy
M5 CLI shell: flag grammar, boot pipeline, runners, OAuth auth shell_app
M6 Host-agnostic TUI primitives + the ratatui render layer tui, tui_render
M7 The ai/agent/mcp/memory compatibility shims facade
M8 Feature-parity audit (the 26-item checklist)
M9 Perf gate + memory model (cold start / RSS / install footprint) release profile, benches
M10 crates.io packaging, license bundling, dist binary distribution, the CI gate Cargo.toml, dist-workspace.toml, about.toml

By the final gate all 11 milestones are done and the parity verdict is 26/26.

Release gates

A publish runs only after the CI gate is green (Cargo has no prepublish hook, so the TS prepublishOnly gate is encoded in .github/workflows/ci.yml):

Gate Command Result
Format cargo fmt --all --check clean
Lint cargo clippy --workspace --all-targets --all-features -- -D warnings exit 0
Supply chain cargo deny check (advisories + licenses + bans + sources) pass
Lineage cargo run -p xtask -- lineage exit 0 (no lineage markers)
Tests cargo nextest run --workspace --all-features + doctests 2062 passing / 0 failing (run twice, no flakes)
Benches cargo bench --workspace --no-run compiles
Binary smoke indusagi --version / indusagi --bogus indusagi 0.13.1 / exit 2
MSRV / static MSRV build (1.96), musl-static "no dynamic linkage" check, perf-gate pass

The exhaustive-match discipline is part of the gate: the runtime's reducer (cadence) is a (&RunSnapshot, Signal) -> Transition step with no _ arm, so adding a Signal variant is a compile error rather than a silently-dropped case. The connector registry is total over ApiKind for the same reason.

Area-by-area parity

Every row below is done. The grouping follows the milestone that delivered it.

Gateway

Area Evidence
Connectors connector_for_api is total over all 11 ApiKind dialectsanthropic, openai-chat, openai-responses, google-generative, google-vertex, bedrock, azure-openai, nvidia, kimi, ollama, and the network-free mock. bedrock is a fail-fast unsupported stub (W-1).
Emission protocol A frozen Emission union with error-as-terminal-emission; a re-iterable Channel; conversion::fold_reply orders thinking → text → tool calls and reassembles split JSON args.
Catalog + credentials One curated catalog — model_cards() returns exactly 13 ModelCards (W-5), a parity-locked count; fluent models() query (by_provider, all); get_card/estimate_cost are pure and offline; OAuth 2.0 + PKCE S256.
Wire framing SSE (WHATWG edge cases) + NDJSON framers; HTTP status → error-kind mapping (401/403 → auth, 429 → rate_limit).

See LLM Gateway.

Runtime and tools

Area Evidence
Pure FSM RunPhase = IdleInvokingStreamingDispatchingCompactingSettled/Faulted; ToolStage = QueuedRunningDone; the reducer is exhaustive with no _ arm.
Steering + budget steering folds into a live run; abort via the cooperative CancellationToken; turn-budget guard; lazy compaction gate (should_compact / find_cut_point / compact, cut-point never orphans a tool_result).
Session DAG content-addressed SHA-256 DAG (core::content_hash) + append-only JSONL store + resume + branch; RunLedger pub/sub.
Tools 12 tools registered — read-only ["read","ls","grep","find","websearch","webfetch"] then mutating ["write","edit","bash","todo_set","todo_read","process"] (W-6) over abstract Fs/Shell seams.
Collections read-only / coding / all collections; tool_box(ToolCollection, Option<String>) one-call assembly.

See Runtime and Capabilities.

Bridges and connectors

Area Evidence
MCP client stdio + streamable-HTTP endpoints over rmcp, server__tool namespacing, schema normalization, per-server fault isolation (spawn_failed / transport / protocol / tool_error / not_connected).
MCP provider host publishes the agent's ToolBox over tools/list / tools/call.
SaaS control the hexagonal SaasBackend port + Composio adapter, enable / execute / connect / status control tools, OAuth-polling state machine, identity-stable hydration cache.

See Interop and Connectors.

Swarm and builder

Area Evidence
Kernel file-backed cell/log with locking + atomic publish, ULID ids, fault kinds.
Coordination dependency-gated ready() + cycle rejection, cursor-exact mailbox, git-worktree isolation, round runner.
Builder the IndusForge flag FSM, blueprint validation, a compile-time-embedded knowledge pack (include_dir), and the redacting Forge session.

See Swarm and Smithy.

CLI

Area Evidence
Flag grammar a 10-flag grammar with mode precedence (help / version / print / wire / repl) mirrored from the source.
Wire + auth NDJSON wire protocol byte-compatible with existing hosts; auth login/refresh/status over a compatible auth.json format.
Exit codes pub async fn run(argv: Vec<String>) -> ExitCode; the binary never calls process::exit, so every destructor runs (TUI restores the terminal, MCP fleet closes, writers flush). --versionindusagi 0.13.1; --bogus → exit 2.

See Shell App and CLI Reference.

TUI

Area Evidence
Rendering flicker-free streaming markdown (tables, highlighted fences) via ratatui's double-buffered cell-diff; word-level structured diffs; tool cards with clamp/expand.
Dialogs the 12 dialogs map 1:1 to the source (model, scoped-models multi-select, session browser, startup picker, settings, theme with live preview, login, logout, OAuth, window/context, tree/fork).
Chrome footer stats with context thresholds, task panel, status line; Esc abort / Ctrl-C exit; queued-message restore.
Persistence live theme switching (P-1, the last parity item, now wired end-to-end and Rust-tested); alternate-screen scrollback + exit transcript print.

See TUI primitives and the render layer.

Compat surface

Area Evidence
Facades indusagi::ai / ::agent / ::mcp shims project the legacy type/role vocabulary onto the green core; indusagi::memory is intentionally empty (W-4); facade::catalog re-exports the one static catalog (R-4 drift gate); facade::session_v3 reads legacy v3 JSONL byte-for-byte.

See Facades.

Cross-cutting

Area Evidence
Cancellation core::CancellationToken is honored end-to-end (model stream, tool runners, web tools); MCP invokes do a pre-flight check only (W-7).
Env registry the core::brand / core::locate modules own every env read — INDUSAGI_HOME, provider key probes — and state resolves through the Locator (precedence: explicit override → INDUSAGI_HOME → OS home); no config read touches std::env directly.
Distribution hygiene lineage gate green; LICENSE / NOTICE / CREDITS.md bundled into each crate sdist; THIRDPARTY.md (from cargo-about) in the dist release tarballs.

See Tracing for the cross-cutting observability seam, and Core for the primitives floor.

Performance characteristics

The headline reason to reach for the Rust edition is footprint and latency. Measured against the Node build:

Metric Improvement vs Node
Cold start ~86× faster
Peak RSS ~30× smaller
Install footprint ~27× smaller

These come from the native-binary substrate plus a release profile tuned for a latency-sensitive agent rather than for size:

[profile.release]
opt-level     = 3            # latency-sensitive agent — NOT "z"
lto           = "thin"
codegen-units = 1
strip         = true
panic         = "abort"

[profile.dist]              # release-artifact profile (max optimization)
inherits      = "release"
lto           = "fat"

The hot paths are guarded by criterion micro-benches that print median / throughput to stdout and are part of the gate (cargo bench --workspace --no-run compiles): session_hash (canonical-JSON encode + SHA-256 content hash on a session node), sse_parse (SSE + NDJSON framers on a chunked body), myers_diff (the hand-rolled Myers O(ND) line diff), fuzzy_match (the file-picker matcher on a 10k-path corpus), and cell_diff (the immediate-mode compose + Buffer cell-diff render loop). The binary crate also has an off-by-default mimalloc feature that swaps in the mimalloc global allocator, kept off so the default build and the criterion baseline run on the system allocator. The perf-gate methodology and the per-bench corpus are described in this section and in the Architecture overview; the headline numbers are also summarized on the Rust edition overview.

Deliberate waivers

The audit records eight intentional deviations from a naive 1:1 port. These are decisions, not gaps; several are pinned by parity tests in the source tree.

ID Waiver Source
W-1 Bedrock is a fail-fast unsupported stub — it assembles region, URL, and the Converse body and touches the credential resolver, then raises gatewayError("unsupported", ...) without any network call. SigV4 signing + the binary event-stream codec are deferred; the aws-sigv4 dep is already declared for a localized drop-in. llmgateway/connectors/bedrock.rs
W-2 On-disk state is fully port-local: the framework home resolves via the Locator (override → INDUSAGI_HOME → OS home, defaulting to .indusagi). The auth.json format stays compatible. core/locate.rs
W-3 The render layer replaces the deleted Ink/React reconciler entirely — state lives in plain Rust structs, every frame is one terminal.draw(...) pass, and ratatui's cell-diff replaces React's virtual-DOM diff; the loader-probing / jsx-runtime subpaths are removed and re-documented. tui_render/
W-4 indusagi::memory (facade::memory) ships intentionally empty, guarded by a parity test; the ~28.7k-LOC hand-written second engine (ml/bot/mcp-core) the TS barrels fronted is deliberately not re-ported. facade/memory.rs, facade/mod.rs
W-5 model_cards() count is 13, not 14 — the plan's "14" was a documented miscount; a there_are_exactly_thirteen_cards test pins it and card_ids_match_the_ts_source_verbatim pins the ids. llmgateway/catalog/cards.rs
W-6 The tool count is 12, not 11 — the checklist row said "11" but listed 12 names; the registry carries the same 12 (READ_ONLY_NAMES + MUTATING_NAMES). capabilities/registry.rs
W-7 MCP invoke cancellation is pre-flight-only; an in-flight rmcp callTool is never raced against the token — threading cancel into the SDK call would be a behavioral change versus TS/Python. interop/protocol_bridge/endpoint.rs
W-8 TUI key decoding stays decode-agnostic: a KeyId is a canonical string (ctrl+shift+alt+<key> order); raw byte decode comes from crossterm upstream, while tui/keys.rs keeps the key-id vocabulary and test corpus. tui/keys.rs, tui/mod.rs

Drift gates

Beyond the waivers, the rebuild encodes a handful of drift gates — tests that fail if a clean-room invariant is broken — so the "one canonical source per type" rule cannot silently regress:

ID Invariant
R-1 The mcp facade rides exactly one core ServerEndpointImpl/ProviderHost; it does not re-implement the protocol.
R-4 There is one model catalog: facade::catalog and facade::ai re-export the gateway's &'static [ModelCard]; the gate asserts the same as_ptr() backs both surfaces, and session_v3 shares the v3 JSONL vocabulary.
R-6 The capabilities grep tool matches the source's grep semantics.

Version drift

The non-code meta files disagree on a few numbers because they were written at different points in the rebuild. Cargo.toml is the single source of truth for the version — every member inherits version.workspace = true, the runtime reads it via indusagi::VERSION = env!("CARGO_PKG_VERSION"), and the number is never hardcoded in code.

Fact Authoritative value Where drift appears
Workspace version [workspace.package] version = "0.1.0" in Cargo.toml CREDITS.md / NOTICE say the Rust line "continues that version line, starting at 0.13.1"; the README's --version smoke test cites indusagi 0.13.1. The Cargo single-source is 0.1.0; the 0.13.x figures are the narrative continuation of the npm line.
Model count 13 curated model_cards() (W-5) a larger "models / providers" figure elsewhere (the routed generated.rs discovery catalog — 600+ generated models routed onto the 11 ProviderId variants) counts the full routed catalog, not the curated cards — different things.
Tool count 12 registered tools (W-6) the checklist row text says "11".
Crate count 3 shipped crates + xtask the README's PLAN//PUBLISHING.md describe the planned 16-crate split that was merged into one crate at M10.

The PLAN/, PARITY_REPORT.md, PERF.md, PROGRESS.md, and WAIVERS.md documents referenced from README.md are planning artifacts; the load-bearing facts (gate results, waivers, counts) live in the source tree itself and are verified by tests, which is what this page cites.

Relationship to the other editions

There are three first-class editions of the same framework, all ported from one TypeScript ground truth:

Edition Docs Distribution Public surface
TypeScript (original / ground truth) /docs npm indusagi v0.13.1 The src/index.ts barrel; subpath exports indusagi/{ai,agent,mcp,memory}.
Python /python pip install indusagi Lazy per-layer subpackages; snake_case, async/await.
Rust (this edition) /rust crates.io indusagicargo install indusagi (binary) / cargo add indusagi (library) The src/lib.rs umbrella barrel; one crate, many modules; async over Tokio.

The Rust edition tracks the TypeScript ground truth module-for-module and is declared at full parity (26/26) — the same subsystems, the same 13-card model catalog, the same v3 JSONL session format (byte-compatible with TS-written files), the same CLI flag grammar and runners, the same OAuth + PKCE login. It shares the eight-waiver vocabulary with the Python edition's parity report (the IDs line up area-by-area: Bedrock stub, port-local state, the not-re-ported facade engine, the empty memory, the 13-card count, the 12-tool count, the pre-flight MCP cancel, the host-owned key decode), differing only where the host ecosystem differs (Python's W-8 is Textual's key decoding; Rust's is crossterm's). Where the Python edition uses lazy per-layer imports, the Rust edition collapses the same layers into one crate's modules reached through the umbrella barrel, trading the wire-up for native-binary footprint and start-up latency.

Where to next