induscode (Rust) Overview
induscodeis the 100%-Rust edition of the terminal-first AI coding agent, built entirely on the publishedindusagiframework crate. Install it withcargo install induscodeto get two equivalent commands —indusrandindusagir— that launch the same agent: a ratatui REPL, a headless print/JSON channel, the capability deck, branchable sessions, and MCP mounting. It is built from scratch in Rust, never transliterated from the TypeScript or Python editions.
induscode is shipped as one merged product crate (crates/induscode): every
former induscode-* library crate is now a pub mod under src/, and the framework
arrives as the single published indusagi umbrella crate (replacing 13 former
indusagi-* path deps). Two [[bin]] targets — indusr and indusagir — share one
async fn main() -> ExitCode in src/bin/main.rs; the live brand is whichever name
the OS launched under. The library surface is induscode::<module> (e.g.
induscode::boot, induscode::conductor); the framework is reached as
indusagi::<module>.
Table of Contents
- Install
- The indusr command
- Feature Overview
- CLI Flags
- Slash Commands
- The Binary Entry
- Crate Layout
- Build
- Relationship to the framework
- Parity with the TS and Python editions
- Where to go next
Install
cargo install induscode # the CLI — installs indusr + indusagir
cargo binstall induscode # prebuilt binary (no compile)
cargo add induscode # the library — depend on the agent (induscode::<module>)
induscode is published to crates.io as one crate that is both a library and a binary.
cargo install induscode installs the CLI; cargo add induscode adds it as a library
dependency (surface induscode::<module>, e.g. induscode::boot, induscode::conductor).
Installing it registers two bin targets, both pointing at src/bin/main.rs:
# crates/induscode/Cargo.toml
[[bin]]
name = "indusr"
path = "src/bin/main.rs"
[[bin]]
name = "indusagir"
path = "src/bin/main.rs"
Both launch the identical agent — use whichever name you prefer. The toolchain floor
is edition 2024, MSRV 1.96 (matching the framework's rust-toolchain.toml pin).
Authenticate by exporting a provider key (ANTHROPIC_API_KEY, etc.) or by storing
one in the local vault:
indusr signin anthropic # store an API key interactively (echo muted)
indusr auth status # show which providers are signed in
The version is single-sourced from Cargo.toml and read back at compile time
(VERSION == env!("CARGO_PKG_VERSION")) — never duplicated as a string literal,
a rule enforced by the cargo run -p xtask -- version gate.
The indusr command
The runner is selected from your flags inside boot::boot_run;
a bare invocation opens the interactive ratatui console, -p/--print runs one-shot
print-and-exit, and --json speaks the headless NDJSON / JSON-RPC line protocol for a
driving parent process.
export ANTHROPIC_API_KEY=sk-ant-...
indusr # interactive ratatui console (current repo)
indusr "fix the failing test in src/auth"
indusr -p "summarise what changed on this branch" # one-shot print, then exit
indusr --json # NDJSON line protocol for a parent process
indusr -m anthropic/claude-sonnet-4-5 -p "hi" # pick a model
indusr --list-models # browse the model / provider catalog
indusr --version # print "<app name> <version>" and exit
| Invocation | Mode | What it does |
|---|---|---|
indusr |
interactive | ratatui REPL — streaming replies, a live tool deck, slash commands |
indusr -p "…" / --print |
one request, prints the final result, exits | |
indusr --json |
line protocol | newline-delimited JSON for a parent process to drive |
indusr auth login / status |
credentials | store / inspect credentials in the local auth vault |
No-session verbs
Several leading verbs are intercepted by route() in main.rs before the boot
handoff — they never need a session, and boot would otherwise drop them into the REPL:
| Verb | Owner | What it does |
|---|---|---|
signin <provider> |
agent vault | Interactive API-key login (defaults to --method api-key) |
signout <provider> |
agent vault | Remove a stored credential for a provider |
auth login | refresh | help |
framework indusagi::shell_app::auth_cli |
Browser (OAuth/PKCE) flows |
auth status [provider] |
agent vault reader | Reconciles framework + multi-account vault shapes |
auth logout |
agent signout path |
The framework auth command has no logout verb |
install | remove | update | list | config |
induscode::launch::pickers |
The extension-package surface over PreferenceStore |
--list-models [filter] |
induscode::launch::catalog |
Prints the model catalog over the live gateway cards |
Because the shipped OAuth client ids are unconfigured sentinels in a default build,
signin <provider> auto-appends --method api-key so it lands on the working
API-key prompt; pass --method oauth (or --oauth) to opt into the browser flow.
Feature Overview
Every subsystem is a pub mod of the merged crate. Each links to its own page under
/rust-cli.
| Feature | Module | What it gives you | Docs |
|---|---|---|---|
| Launch front door | launch |
The single FLAG_SPECS table, the tolerant argv reader, @file attachments, the credential vault, OAuth flows, the model catalog, and pickers. |
Launch |
| Boot pipeline | boot |
boot_run(argv, BootIo) -> ExitCode: short-circuit verbs, stage pipeline, runner selection, upgrade fold, always-drained closables. |
Boot |
| Conductor | conductor |
The session core — wraps one indusagi::runtime::Agent, projects RunEvent into a stable SessionSignal stream, branchable transcript, fault/retry, fallback model, compaction. |
Conductor |
| Window budget | window_budget |
Token measurement, is_over_budget gating, slice planning, and create_condenser → the conductor's CondenseFn. |
Window Budget |
| Capability deck | deck |
The tool registry over indusagi::capabilities (read/write/bash/edit/grep/find/websearch/webfetch/todo/process) + in-deck cards + the MCP bridge ledger. |
Capability Deck |
| Runtime bridge | runtime_bridge |
Per-model routing — framework gateway vs. an external child runtime — as a ModelInvoker, plus tracing-sink wiring. |
Runtime Bridge |
| Addons | addons |
The loadable-extension contract: hooks, tool interceptors, contributed commands/tools, tiered TOML/subprocess loader. | Addons |
| Console (UI) | console |
The interactive ratatui surface: one immutable ConsoleState, a pure console_reducer, immediate-mode draw, 13 overlays, mount_console. |
Console Overview |
| Channels | channels |
The non-interactive surfaces: the one-shot text/NDJSON channel and the long-lived JSON-RPC 2.0 link server over the NDJSON framer. | Channels |
| Briefing | briefing |
The budget-aware system-prompt assembler: section composer, CLAUDE.md/AGENTS.md context docs, $arg macros, SKILL.md cards. |
Briefing |
| Transcript export | transcript_export |
The publish-time HTML/Markdown builder: the SGR machine, WCAG theme_bridge, page template, publish. |
Transcript Export |
| Insight | insight |
The observability plane over indusagi::tracing: probe/signal vocab, NDJSON serialize/replay, an agent scrubber, a fan-out channel and collector sink. |
Insight |
| Core | core |
The BRAND record, single-source VERSION, BIN_NAMES, create_workspace, PreferenceStore, plus the folded kit / settings / sessions leaves and lineage guardrails. |
Core |
Models and providers come from the framework catalog — see the framework's LLM Gateway and the agent's Models reference.
CLI Flags
The single source of truth is launch::flags::flag_specs() — a &[FlagSpec] walked
by both the parser (launch::parser::read_invocation) and the usage renderer
(launch::usage::render_usage), so --help can never drift from what the parser
accepts. Each entry carries a canonical name, aliases, a FlagKind (Boolean
| String | Number | List), a FlagGroup usage section, and a describe
string:
// crates/induscode/src/launch/flags.rs
pub struct FlagSpec {
/// Canonical long spelling, leading dashes included (e.g. "--model").
pub name: &'static str,
/// Alias spellings; all alias hits normalise to `name` ("--rpc" → "--json").
pub aliases: &'static [&'static str],
pub kind: FlagKind, // Boolean | String | Number | List
pub group: FlagGroup, // Output | Model | Context | Tools | Meta (usage section)
pub describe: &'static str,
}
The full 18-flag grammar (describe strings copied verbatim from source, except
--thinking, whose text is computed at startup from the THINKING_EFFORTS rungs):
| Flag | Aliases | Kind | Purpose |
|---|---|---|---|
--print |
-p |
Boolean | Run a single request, print only the result, and exit. |
--json |
--rpc |
Boolean | Speak the headless line protocol for a driving parent process. |
--interactive |
-i |
Boolean | Force the interactive session even when a prompt is supplied. |
--model |
-m |
String | Select the model, provider-qualified or bare (e.g. provider/name). |
--fallback-model |
— | String | Model to switch to mid-turn when the selected model is overloaded (HTTP 529). |
--account |
— | String | Authenticate the run with a named stored credential account. |
--thinking |
— | String | Set the reasoning effort (one of: …) — the rungs are interpolated from THINKING_EFFORTS. |
--list-models |
— | String | List the available models (optionally filtered by a substring) and exit. |
--cwd |
— | String | Scope the run to a working directory (default: the current directory). |
--system |
— | String | Replace the built-in system prompt with the given text. |
--append-system |
— | String | Append extra text after the system prompt. |
--resume |
-r |
Boolean | Pick a previous session to resume. |
--continue |
-c |
Boolean | Continue the most recent session in this directory. |
--tools |
— | List | Allow only the named built-in tools (comma-separated or repeated). |
--no-tools |
— | Boolean | Disable every built-in tool for this run. |
--mcp |
— | List | Attach an external MCP server endpoint (comma-separated or repeated). |
--help |
-h |
Boolean | Show this usage and exit. |
--version |
-v |
Boolean | Show the version and exit. |
The reader is deliberately tolerant of unknown flags (the extension-flag escape
hatch), so the agent's exit-2 usage convention is enforced separately by
unknown_leading_flag() in main.rs: an unrecognized leading -… token reports
on stderr and exits 2. See Launch for the parser
internals and @file attachment inlining.
Slash Commands
Inside the interactive console, / opens the command palette. The catalog is built
by console::slash::build_catalog(&DynamicCommandSources) — static rows first, then
dynamic rows discovered from local skills and macros. The static SlashCommand set
(by registered name):
| Command | What it does |
|---|---|
/help |
List the command catalog |
/clear, /new |
Reset the transcript / start a fresh session |
/resume, /session, /timeline |
Navigate persisted sessions and branches |
/branch, /name |
Branch a prior turn / rename the session |
/model, /models-for |
Switch model / show models for a capability |
/thinking |
Set the reasoning effort |
/login, /logout, /keys |
Credential management from inside the console |
/mcp |
Inspect and mount MCP servers |
/memory |
View and edit agent memory |
/composio |
SaaS connector actions |
/settings |
Browse and change preferences |
/summarize-context |
Compact the conversation window |
/cost |
Show token / cost accounting for the session |
/plan |
Toggle plan mode |
/export, /share, /copy |
Publish or copy the transcript |
/reload, /debug, /whats-new |
Reload addons, toggle diagnostics, changelog |
/exit, /quit |
Leave the console |
The registry is pure and typed (SlashCommand / SlashContext / SlashOutcome) —
see Slash Commands.
The Binary Entry
src/bin/main.rs is the single OS exit point — async fn main() -> ExitCode is the
only place an exit code reaches the OS. Returning ExitCode (rather than calling
process::exit) lets the tokio runtime drain and destructors run, so the terminal's
raw / alt-screen mode is restored on the way out. main does three things in order:
- Brand off
argv[0]'s basename —resolve_brand()picksindusorindusagifromBIN_NAMES, falling back toBIN_NAMES[0]("indus") for a renamed symlink or a missingargv[0]. (A.exesuffix is stripped so a Windows launch still brands.) - Install the single
[MCP]console-noise filter —install_mcp_noise_filter()is idempotent and a no-op when the debug env var (indusagi::core::env_name("debug")→INDUSAGI_DEBUG) is set, so diagnostics are never hidden. - Route the remaining argv —
route()short-circuitsauth, the meta flags (--help/--version),signin/signout, the package verbs, and--list-models, then hands every real run toboot_run.
// crates/induscode/src/bin/main.rs
#[tokio::main]
async fn main() -> ExitCode {
let mut args = std::env::args();
let argv0 = args.next();
let brand = resolve_brand(argv0.as_deref());
install_mcp_noise_filter();
let rest: Vec<String> = args.collect();
route(brand, rest).await
}
The boot handoff is boot_run(rest, production_io()).await, where production_io()
builds a BootIo { output, input } over a stdout/stderr OutputSink and a
blocking-stdin InputSource. boot_run's resolved ExitCode is adopted unchanged.
Crate Layout
The agent is one virtual Cargo workspace (members = ["crates/induscode", "crates/induscode-testkit", "xtask"]). The 13 former induscode-* library crates
and the former induscode-cli are all merged into the single induscode crate; the
framework is the one published indusagi umbrella crate (version = "0.1.0"):
# crates/induscode/Cargo.toml
[dependencies]
indusagi = { version = "0.1.0", features = ["swarm"] }
The module map (former crate → pub mod) declared in src/lib.rs:
| Former crate | pub mod |
Responsibility |
|---|---|---|
induscode-core |
core |
brand, VERSION, BIN_NAMES, workspace, settings, sessions, kit, guardrails |
induscode-launch |
launch |
flag grammar / usage, @file, credentials, OAuth, model catalog, pickers |
induscode-boot |
boot |
argv → invocation, workspace prep, runner selection, upgrades |
induscode-conductor |
conductor |
the conversation loop — turns, tools, fault/retry, branch/resume, compaction |
induscode-window-budget |
window_budget |
token accounting + context-window compaction policy |
induscode-deck |
deck |
the tool registry + in-deck cards + MCP bridge |
induscode-runtime-bridge |
runtime_bridge |
framework / external-runtime model routing |
induscode-addons |
addons |
loadable extensions: hooks, interceptors, commands, tools |
induscode-console |
console |
the interactive ratatui surface |
induscode-channels |
channels |
print + line-protocol headless surfaces |
induscode-briefing |
briefing |
system-prompt / context assembly |
induscode-transcript-export |
transcript_export |
session persistence + HTML/Markdown export |
induscode-insight |
insight |
tracing, sinks, replay, secret redaction |
Cargo feature gates (defaults ["rustls", "mcp", "tui", "oauth"]): tui pulls the
framework render layer for the console; mcp forwards to indusagi/mcp; swarm
gates subagent delegation; composio adds the SaaS adapter; cli-bridges gates the
external-runtime child transport; mimalloc is an opt-in global allocator. A headless
build drops the console entirely (see below).
Build
cargo build --workspace
cargo build --release -p induscode # the release/dist profile (lto)
# headless — no console / no render layer:
cargo build -p induscode --no-default-features --features "rustls,anthropic"
# repo automation (clean-room + single-source gates):
cargo run -p xtask -- lineage # source-hygiene gate
cargo run -p xtask -- version # literal-VERSION single-source gate
The clean-room posture is enforced as blocking gates: xtask lineage exits non-zero
if any provenance marker appears in non-test source, xtask framework-via-deps
asserts the framework is reached only through declared crate deps, and xtask version
forbids any string-literal version outside Cargo.toml.
Relationship to the framework
induscode is built on top of the indusagi framework crate and reuses
it wholesale. The framework supplies the LLM gateway, the agent loop
the conductor wraps (indusagi::runtime::Agent + the ModelInvoker seam), the
TUI primitives the console renders over (indusagi::tui /
indusagi::tui_render), the MCP client, the
capabilities the deck manages, and
tracing that insight wraps. Every framework module is
reached as indusagi::<module> (core, runtime, llmgateway, tracing,
capabilities, tui, interop, connectors_saas, swarm, smithy, tui_render,
facade, shell_app).
The boundary is sharp: the conductor projects the framework's coarse 7-event
RunEvent stream into its own stable SessionSignal stream so the product surface
evolves independently; the deck manages the framework's native tools rather than
re-authoring them; the auth subcommand delegates login/refresh/help to
indusagi::shell_app::auth_cli::run_auth_command, while auth status and auth logout are handled by the agent's own multi-account vault (classify_auth_verb in
main.rs routes status → the agent reader and logout → the agent signout
path, because the framework command can neither see api-key sign-ins nor log out).
The brand reads env_name("debug") from the framework so the agent and framework
agree on the env prefix.
Parity with the TS and Python editions
induscode is the third edition of the same coding agent, reverse-engineered to a
behavioral spec — never transliterated:
| Edition | Package | Bins | Framework | Docs |
|---|---|---|---|---|
| TypeScript | indusagi-coding-agent |
indus / indusagi |
indusagi (npm) |
TS CLI |
| Python | induscode (PyPI) |
pindus / induscode |
indusagi (PyPI) |
Python CLI |
| Rust | induscode (crates.io) |
indusr / indusagir |
indusagi (crates.io) |
this page |
The three share the same surface — the FLAG_SPECS flag grammar, the slash-command
catalog, the runner trio (interactive / print / line-protocol), and the boot
short-circuit verbs. The differences are idiomatic to each language: the Rust edition
is a single merged crate of pub mods (vs. the Python lazy PEP-562 barrel and the TS
two-package split), it returns ExitCode rather than calling process::exit, and the
console is immediate-mode ratatui over a pure reducer rather than Ink/React or
Textual. See the TS /cli and Python /python-cli overviews
for the matching surface in those languages.
Where to go next
- Boot — the launch pipeline and runner registry
- Launch — the flag grammar, credentials, model catalog
- Conductor — the session core wrapping one Agent
- Console Overview — the interactive ratatui shell and its overlays
- Capability Deck — the tool set and the MCP bridge
- Channels — the print + JSON-RPC headless surfaces
- Auth and Settings — credentials and preferences
- Parity — the clean-room rebuild and its TS/Python lineage
Notes:
- The crate is
induscodeon crates.io; the two[[bin]]targets areindusrandindusagir, bothsrc/bin/main.rs. The internal brand (BIN_NAMES, the process title, the--versionapp name) isindus/indusagi— so a launch of the installedindusr/indusagirresolves throughresolve_brand'sBIN_NAMES[0]fallback to theindusbrand (--versionprintsindusagi <version>, sinceBRAND.app_name == "indusagi"). - Requires edition 2024, MSRV 1.96; the surface is
async/awaitand snake_case throughout. - The version is single-sourced from
Cargo.tomland read back viaenv!("CARGO_PKG_VERSION")— never duplicated. async fn main() -> ExitCodeis the only OS exit point, so the terminal's raw/alt-screen mode is always restored.
