This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Sage Agent is a Rust-based LLM agent system for software engineering tasks, originally inspired by ByteDance's Trae Agent. This is a multi-crate Rust workspace with async architecture built on Tokio.
# Build in debug mode
cargo build
# Build optimized release
make release
# Install CLI globally
make install
# or
cargo install --path crates/sage-cli# Run all tests
make test
# or
cargo test
# Unit tests only
make test-unit
# Integration tests only
make test-int# Run the CLI in dev mode (interactive)
make dev
# or
cargo run -p sage-cli
# Run with a task
cargo run -p sage-cli -- "your task"
# Print mode (non-interactive, execute and exit)
cargo run -p sage-cli -- -p "your task"
# Resume most recent session
cargo run -p sage-cli -- -c
# Code quality checks
make clippy # Linting
make fmt # Formatting
make check # Type checking without building
# Full development cycle
make quick # fmt + clippy + test
make ci # Full CI check# Run example code
make examples
cargo run --example basic_usage
cargo run --example markdown_demo
cargo run --example ui_demo
cargo run --example trajectory_demoThis is a Rust workspace with four main crates:
sage-core/: Core library containing agent execution engine, LLM providers, tool system, and UI componentssage-cli/: Command-line interface with interactive mode and progress indicatorssage-sdk/: High-level SDK for programmatic integrationsage-tools/: Collection of built-in tools (bash, edit, json_edit, codebase_retrieval, task management, etc.)
crates/sage-core/src/agent/: Core agent execution logic and state managementcrates/sage-core/src/llm/: LLM client implementations for multiple providerscrates/sage-core/src/tools/: Tool registry and execution systemcrates/sage-core/src/ui/: Terminal UI components with animationsexamples/: Usage examples for SDK and CLI featuresdocs/: Comprehensive documentation structuretrajectories/: Execution recording files (gitignored)
The project uses JSON configuration files:
- Main config:
sage_config.json - Example:
sage_config.json.example - Template:
configs/sage_config.example.json
Configuration supports multiple LLM providers (OpenAI, Anthropic, Google) with environment variable substitution.
- Multi-LLM Support: OpenAI, Anthropic, Google, and other providers
- Rich Tool Ecosystem: Built-in tools for code editing, bash execution, codebase retrieval
- Interactive CLI: Terminal UI with animations and progress indicators
- Trajectory Recording: Complete execution tracking and replay
- SDK Integration: Programmatic usage through high-level SDK
- Async Architecture: Built on Tokio for concurrent operations
The project has extensive documentation in docs/:
docs/user-guide/: User documentationdocs/development/: Developer guides and contribution infodocs/architecture/: System design documentationdocs/api/: API referencedocs/planning/: Project roadmap and TODO lists
Tests are organized as:
- Unit tests:
cargo test --lib - Integration tests:
cargo test --test integration_test - Examples serve as integration tests:
make examples
- Follow Rust 2024 edition standards
- Use workspace dependencies defined in root
Cargo.toml - All crates share common version (0.1.0) and metadata
- Async code uses Tokio runtime
- Error handling with
anyhowandthiserror - Logging with
tracing - CLI built with
clap
Automated checks via VibeGuard:
make guard # Report duplicate types, nested locks, unwrap usage
make guard-strict # Same but exit 1 on violations (CI mode)| Guard | Rule | What it detects |
|---|---|---|
check_duplicate_types.sh |
RS-05 | Same-named pub struct/enum across files |
check_nested_locks.sh |
RS-01 | Multiple .lock()/.read()/.write() in one function |
check_unwrap_in_prod.sh |
RS-03 | .unwrap()/.expect() in non-test code |
To suppress false positives for duplicate types, add type names to .vibeguard-duplicate-types-allowlist.
Enforced by crates/sage-core/tests/architecture_guards.rs, run via cargo test.
| Guard | ID | Rule |
|---|---|---|
| test_error_types_use_thiserror | RS-ERR-01 | Error 枚举用 thiserror,禁手写 Display |
| test_no_result_string_in_core | RS-ERR-02 | sage-core 禁 Result<_, String> |
| test_provider_pattern_consistency | RS-LLM-01 | Provider 必须有 struct + chat + chat_stream |
| test_no_rwlock_on_mcp_client | RS-MCP-01 | McpClient 禁 RwLock 包装 |
| test_file_size_limits | RS-SIZE-01 | 非测试文件 ≤500 行 |
| test_no_bare_generic_type_names | RS-NAME-01 | pub 类型禁裸泛型名 |
Run: cargo test --package sage-core --test architecture_guards -- --nocapture
- NEVER add deprecated type aliases or shims for backward compatibility
- When renaming types, functions, or APIs, update all references directly
- Do not preserve old names with
#[deprecated]annotations - Breaking changes are acceptable - update the version number accordingly
- Treat acronyms as words in PascalCase:
LlmClient(notLLMClient),HttpRequest(notHTTPRequest) - Correct examples:
LlmProvider,SseDecoder,McpServer,CliConsole - Keep files under 200 lines - split large modules into focused submodules
These rules are derived from real bugs found in this codebase. Every rule corresponds to a past incident.
- NEVER use
asfor narrowing integer casts (e.g.,u32 as i32,u64 as u32). Always useTryFrom/try_fromand handle the error. Example:i32::try_from(pid)?instead ofpid as i32 - NEVER use
asto convertf64to integer types. Always validate withis_finite()and range checks first, then cast. Provide a sensible default for invalid values (NaN, Infinity, negative)
- NEVER use
Vec::remove(0)orVec::drain(..1)for FIFO/ring-buffer patterns. It is O(n) per call and O(n²) in loops - Use
VecDequewithpush_back()/pop_front()instead - Similarly, NEVER use
String::remove(0)in a loop. Usedrain(..n)to remove multiple leading chars in one O(n) pass
- NEVER call
tokio::spawn()without storing the returnedJoinHandle. Fire-and-forget tasks leak resources and silently swallow panics - Store handles in the parent struct and
abort()them inDrop - If a task is truly fire-and-forget, document why explicitly
- NEVER return
Ok(())from a function that doesn't actually do what its signature promises. If a feature is not implemented, returnErrwith a clear message, or mark the functionunimplemented!()/todo!() - Specifically: do not accept
&mut Commandand silently skip modifying it. Do not usestd::mem::forgetto "keep things alive" as a workaround
- NEVER use
std::mem::forgetto extend lifetimes. It leaks the resource. UseArc, struct fields, or explicit ownership transfer instead
- Cross-module shared types MUST be defined in
crate::types/(e.g.,MessageRole,ToolCall,LlmProvider,TodoItem). Original modules re-export viapub use crate::types::TypeName; types/has zero dependencies on other sage-core modules — only external crates (serde, chrono, thiserror, etc.)- NEVER duplicate a type definition across modules. If two modules need the same type, move it to
types/and re-export - Generic type names MUST have a domain prefix when shared:
ToolErrornotError,TodoStatusnotStatus - NEVER create identity type aliases (e.g.,
type Foo = Foo;) — use re-exports instead