Rules and conventions for AI assistants and contributors working on aionrs.
aionrs is a multi-provider AI agent CLI written in Rust. It connects to LLM providers (Anthropic, OpenAI, AWS Bedrock, Google Vertex AI), orchestrates built-in tools (Read, Write, Edit, Bash, Grep, Glob, Spawn), supports MCP servers, skills, hooks, and long-term memory. It also exposes a JSON stream protocol for host integration (e.g. Electron-based AionUI).
Tech stack: Rust 2021 edition, stable toolchain, Cargo workspace under crates/.
Dependencies flow downward — never introduce circular or upward references.
| Layer | Crate | Responsibility |
|---|---|---|
| Bottom | aion-types |
Shared provider-neutral data types (LLM, message, tool) — zero internal deps |
| Bottom | aion-compact |
Context compression algorithms (folding, sanitization, tokenization) |
| Mid | aion-config |
Configuration, ProviderCompat, auth, hooks, cross-platform shell helpers |
| Mid | aion-protocol |
JSON stream protocol (events, commands, approval manager) for host integration |
| Mid | aion-providers |
LLM provider implementations (Anthropic, OpenAI, Bedrock, Vertex) |
| Mid | aion-tools |
Built-in agent tools (Read, Write, Edit, Bash, Grep, Glob, Spawn) |
| Mid | aion-mcp |
MCP (Model Context Protocol) client |
| Mid | aion-skills |
Skills system (prompt snippets, hooks, permissions, shell expansion) |
| Mid | aion-memory |
Long-term cross-session memory (user prefs, feedback, project context) |
| Top | aion-agent |
Agent engine, session management, orchestration |
| Top | aion-cli |
CLI binary entry point |
When adding new functionality, place it in the lowest crate where it
semantically belongs. Don't create a new crate just for one shared function.
Run cargo metadata to verify dependency changes fit the graph.
cargo build # Build
cargo test # Run all tests
cargo clippy # Lint
cargo fmt --all # Format (CI enforces this)Pushing code: always use just push instead of git push.
It runs fmt → clippy → test before pushing, preventing CI failures.
Supports the same arguments as git push (e.g. just push -u origin branch).
cargo clippymust pass without warningscargo fmtmust pass without diffs- Comments in English, commit messages in English
- Error handling:
thiserrorfor public API error types (structured, matchable)anyhowfor internal/application-level error propagation- Never silently swallow errors; never
unwrap()in production code unless the invariant is proven and commented
- Each module (
.rsfile) follows the single responsibility principle — one clear purpose per file - Keep files under 1000 lines; extract sub-modules when approaching the limit
- Organize by domain responsibility, not by type
This is the single most important rule for this codebase.
Handle provider differences through the ProviderCompat configuration
layer, not through hardcoded conditionals.
// WRONG: hardcoded provider detection
if self.base_url.contains("api.openai.com") {
body["max_completion_tokens"] = json!(max_tokens);
}
// CORRECT: read from compat config
let field = self.compat.max_tokens_field.as_deref().unwrap_or("max_tokens");
body[field] = json!(request.max_tokens);If you need a new compat behavior:
- Add an
Option<T>field toProviderCompat - Set its default in the appropriate preset function (e.g.
openai_defaults()) - Use it in provider code via
self.compat.field_name
All providers implement the LlmProvider trait. The engine sees only
provider-neutral types (LlmRequest, LlmEvent, Message, ContentBlock).
Format conversion happens inside each provider's build_messages() /
build_request_body().
Deep dive: see docs/providers.md for provider setup, auth, aliases, and profile inheritance.
Any platform-specific behavior (paths, permissions, shell commands, line endings, etc.) must be wrapped in a single centralized function. All call sites use that function — never scatter raw platform detection across multiple crates or modules. See Cross-Platform for concrete rules.
If multiple crates need the same functionality, extract it to the appropriate existing crate in the dependency graph — don't copy-paste or reimplement. Choose the extraction target based on where it semantically belongs and where it minimizes dependency changes.
CI runs on macOS, Linux, and Windows. Local dev can only test the
current platform's #[cfg(...)] code — other platform branches are
verified by CI alone.
- Never hardcode platform paths (
/tmp/...,C:\...) in production code. UsePath::join(),dirs::config_dir(),tempfile::tempdir(), etc. - In tests, hardcoded Unix paths (
Path::new("/foo/...")) are fine for pure string operations (join, display) or nonexistent-path error handling. Only add#[cfg(unix)]/#[cfg(windows)]variants when the path is passed tois_absolute(),validate_memory_path(), or similar platform-sensitive checks. - Use
std::path::Component::Normal(not byte length) when checking path depth — prefix/root components differ across platforms.
- All shell invocations must go through
aion_config::shellmodule (shell_command()orshell_command_builder()). - Never call
Command::new("sh"),Command::new("bash"), orCommand::new("cmd")directly — these are platform-specific. - External CLI tools that differ across platforms (e.g.
grepvsfindstr) must usecfg!(windows)branches or equivalent platform-aware selection.
| Location | What goes there |
|---|---|
Inline #[cfg(test)] in each .rs file |
Unit tests for that module's internals |
crates/<crate>/tests/ |
Integration tests for that crate |
Unit tests target internal logic and code paths. Integration tests target functional requirements and public API — write them from the spec, not from reading the implementation.
Every test must verify a meaningful behavior or edge case. No trivial tests that just assert the happy path without checking boundaries, error conditions, or non-obvious logic.
Key references in docs/ (don't duplicate their content here):
| Document | Covers |
|---|---|
| getting-started.md | Installation, CLI usage, config format and cascading precedence |
| providers.md | Provider setup, auth, ProviderCompat, custom aliases, profiles |
| tools.md | Built-in tool reference and execution flow |
| skills.md | Writing skills, front matter, shell expansion, conditional activation |
| mcp.md | MCP server integration, transport types, deferred loading |
| advanced.md | Sub-agents, hooks, memory, plan mode, context compression |
| json-stream-protocol.md | JSON Lines protocol spec for host integration (e.g. AionUI) |
| troubleshooting.md | Common errors and solutions |