Parity & Lineage
The Rust edition is a clean-room, 100% Rust rebuild of the
indusagiframework, 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
- Lineage and license posture
- The milestone roadmap
- Release gates
- Area-by-area parity
- Performance characteristics
- Deliberate waivers
- Drift gates
- Version drift
- Relationship to the other editions
- Where to next
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 at0.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 dialects — anthropic, 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 = Idle → Invoking → Streaming → Dispatching → Compacting → Settled/Faulted; ToolStage = Queued → Running → Done; 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. |
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). --version → indusagi 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 indusagi — cargo 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
- Architecture — the module map and dependency direction this audit walks; it also covers the perf-gate methodology behind the headline numbers.
- Getting Started — build, embed, and run the binary.
- Core — the primitives floor (cancellation, locate, canonical hash) the parity invariants ride on.
- LLM Gateway, Runtime, Capabilities — the audited live surfaces.
- CLI Reference — the flag grammar and exit-code contract.
- Other editions: the TypeScript docs and the Python parity report.
