Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

20 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Rust Agent (rust-agent)

A powerful, recursive, tool-equipped Command Line (CLI) AI Agent written in Rust, leveraging the Rig LLM framework and OpenRouter to enable interactive conversational sessions with local sqlite turn tracing, code execution, and sandboxed filesystem manipulation.


πŸ“– Table of Contents

  1. System Overview & Goals
  2. Architectural Blueprint
  3. Key Technical Choices (Why Rust & These Crates?)
  4. File & Directory Structure
  5. Getting Started & Configuration
  6. Core Workflows
  7. Safety & Sandboxing Details
  8. Guide for AI Agents (How to Read & Extend)

🎯 System Overview & Goals

The primary goal of rust-agent is to provide a local interactive terminal (REPL) where a user can converse with an advanced LLM (specifically DeepSeek V4 via OpenRouter by default) that has direct, programmatic access to its host machine under safe sandboxed boundaries.

The agent can:

  • List, read, and write files in the current working directory (the directory from which you launch rust-agent).
  • Run local system shell commands (e.g. running compilers, build scripts, tests).
  • Template prompts using dynamic variable interpolation.
  • Perform keyword, recent, or tool-specific searches on its own conversation history using an embedded SQL trace database.
  • Hierarchical Memory: Retains active memory using a 16k token rolling conversation window (L1), summarizes evicted turns into a 4k token knowledge graph wiki in SQLite (L2), and archives full transcripts in the database (L3).
  • Recursive delegation: Delegate sub-tasks to nested instances of itself (sub-agents) with similar tool sets.

All persistent data (database, rules, prompt templates) lives in ~/.rust-agent/ so the binary works identically from any directory once installed globally.


πŸ“ Architectural Blueprint

The project is written in idiomatic Rust and is structured into a main application module and several helper modules. Below is a high-level component diagram showing how data and control flows:

graph TD
    User([User in REPL]) -->|User Prompt| Main[src/main.rs]
    Main -->|Get Preambles & System Info| Rules[src/rules.rs]
    Rules -->|Read OS Rules| RulesMD[(rules.md)]
    Main -->|Initialize/Update Session & Turns| Trace[src/trace.rs]
    Trace -->|Query / Insert Traces & Wiki| SQLite[(traces.db)]
    Main -->|Create Agent| AgentBuilder[src/agent.rs]
    AgentBuilder -->|Uses| Rig[Rig Framework]
    AgentBuilder -->|Register| Tools[src/tools/mod.rs]
    
    subgraph Tools Area
        Tools --> FSTool[Filesystem Tool]
        Tools --> ExecTool[Code Exec Tool]
        Tools --> PromptTool[Prompt Template Tool]
        Tools --> RecurTool[Recursive Agent Tool]
        Tools --> SearchTool[Trace Search Tool]
    end
    
    FSTool -->|Sandbox check| Hooks[src/hooks.rs]
    ExecTool -->|Safety check| Hooks
    RecurTool -->|Spawn Sub-Agent| AgentBuilder
    
    Main -->|Execute Turn| RigAgent[Rig Agent Instance]
    RigAgent -->|Interventions & Formatting| CLI[src/cli.rs]
    CLI -->|Intercepts Tracing Logs| User
Loading

πŸ›  Key Technical Choices (Why Rust & These Crates?)

To ensure optimal performance, reliability, and ease of deployment, we chose Rust. Below is the rationale for each major crate dependency in Cargo.toml:

  1. rig (v0.39):
    • Why: Rig is a modern, lightweight, and highly ergonomic Rust framework for building LLM applications. Unlike python-based alternatives (like LangChain), Rig is compiled, strongly typed, and fits perfectly into Rust's async model. It simplifies the definition and registration of tools using standard traits, and manages provider communication cleanly.
  2. tokio (v1 with full features):
    • Why: Rig operates asynchronously, sending HTTP requests to LLM APIs and running concurrent logic. Tokio is the de facto standard async runtime in Rust, providing a robust multi-threaded task scheduler, timers, and filesystem/network utilities.
  3. rusqlite (v0.32 with bundled feature):
    • Why: Tracing is critical for LLM agents (for debugging, RAG, and self-inspection). SQLite is a zero-setup, serverless database engine. The bundled feature ensures SQLite compiles statically, eliminating external C-library dependencies.
  4. tracing & tracing-subscriber (v0.1 & v0.3):
    • Why: Rust's standard logging library. Rig emits diagnostic tracing span logs during tool executions. We implement a custom tracing_subscriber::Layer to parse and format these events into beautiful, colorized terminal outputs in real-time, filtering out internal framework noise.
  5. serde & serde_json (v1):
    • Why: The standard serialization/deserialization framework in Rust. Crucial for converting tool arguments/results to JSON and serializing token usage stats.
  6. thiserror & anyhow (v2 & v1):
    • Why: Standard error handling practices in Rust. We use thiserror for writing structured, domain-specific enums in modules (like tool execution errors) and anyhow for top-level application logic where error propagation with backtrace/context is desired.
  7. dotenvy (v0.15):
    • Why: Safely extracts secrets (e.g. OPENROUTER_API_KEY) from a local .env file without committing credentials to source control.
  8. uuid (v1 with v4 feature):
    • Why: Generates cryptographically secure session IDs for session logging and tracing.

πŸ“‚ File & Directory Structure

.
β”œβ”€β”€ Cargo.toml               # Cargo package configuration and dependencies
β”œβ”€β”€ Cargo.lock               # Pinned dependencies
β”œβ”€β”€ rules.md                 # Default safety rules (copied to ~/.rust-agent/ on first run)
β”œβ”€β”€ prompts/                 # Default prompt templates (copied to ~/.rust-agent/prompts/ on first run)
β”‚   β”œβ”€β”€ code_review.md
β”‚   β”œβ”€β”€ debug.md
β”‚   └── explain.md
└── src/                     # Rust source code
    β”œβ”€β”€ main.rs              # App entry point, first-run setup, REPL loop, turn persistence
    β”œβ”€β”€ agent.rs             # Agent builder: Rig config, model choice, tool registration
    β”œβ”€β”€ cli.rs               # Colorized console layer, banners, spinner, and tracing interceptor
    β”œβ”€β”€ config.rs            # Global config directory (~/.rust-agent/) and first-run initialization
    β”œβ”€β”€ hooks.rs             # Core safety filters for file manipulation and command executing
    β”œβ”€β”€ rules.rs             # Rule parser, environment analyzer, and preamble formatter
    β”œβ”€β”€ trace.rs             # TraceStore (SQLite tables, inserts, searches, wiki, embeddings)
    └── tools/               # Agent-usable tools implementing `rig::tool::Tool`
        β”œβ”€β”€ mod.rs           # Shared ToolContext for associating actions with database turns
        β”œβ”€β”€ agent_tool.rs    # Recursive sub-agent delegation tool
        β”œβ”€β”€ code_exec.rs     # PowerShell command executor tool
        β”œβ”€β”€ filesystem.rs    # PWD-sandboxed file reader/writer/lister tool
        β”œβ”€β”€ prompt.rs        # Lazy-loaded template loading and rendering tool
        └── trace_search.rs  # LLM-facing interface to query SQLite traces (keyword, recent, by_tool, semantic)

~/.rust-agent/               # Global config directory (auto-created on first run)
    β”œβ”€β”€ .env                 # Place your OPENROUTER_API_KEY here
    β”œβ”€β”€ rules.md             # Editable agent behaviour rules
    β”œβ”€β”€ prompts/             # Editable prompt templates
    β”‚   β”œβ”€β”€ code_review.md
    β”‚   β”œβ”€β”€ debug.md
    β”‚   └── explain.md
    └── traces.db            # SQLite database β€” all sessions, all projects

πŸš€ Getting Started & Configuration

Prerequisites

  1. Install Rust (which includes cargo).
  2. Obtain an API Key from OpenRouter.

Option A β€” Global Install (Recommended)

Install the binary globally so you can use rust-agent from any directory:

cargo install --path .

Then set your API key (pick one method):

Method 1 β€” Config file (created automatically on first run):

# After running rust-agent once, edit the generated file:
notepad $env:USERPROFILE\.rust-agent\.env
# Add this line:
# OPENROUTER_API_KEY=your-key-here

Method 2 β€” System environment variable (persists across all terminals):

[System.Environment]::SetEnvironmentVariable("OPENROUTER_API_KEY", "your-key-here", "User")

Now run from any directory:

rust-agent

The agent will operate on the current directory's files.

Option B β€” Local Development Run

# Create a .env in the project root
copy NUL .env  # then edit it
# Add: OPENROUTER_API_KEY=your-key-here

cargo run --release

Others Can Install From GitHub

Once your repo is public on GitHub:

cargo install --git https://github.com/YOUR_USERNAME/rust-agent

Type prompts in the REPL. Use /exit or Ctrl+D to end the session. End a line with \ for multiline input.


πŸ”„ Core Workflows

REPL Loop & Turn Management

Located in main.rs, the REPL runs an async loop that:

  1. Flushes a prompt symbol (>) to the terminal.
  2. Accepts user input via stdin (supports multiline input with \ continuation).
  3. Lines ending with \ are buffered for multiline input.
  4. Inserts a placeholder turn record in the turns table in SQLite to retrieve a unique turn_id.
  5. Stores the turn_id inside a thread-safe ToolContext mutex.
  6. Invokes the Rig agent asynchronously with a 120-second timeout.
  7. The agent performs thinking, calls tools, and writes tool results back to SQLite via the turn_id context.
  8. Prints the final response to the user with a token usage summary.
  9. Resets the shared turn_id in the context.

Recursive Agent Sub-Delegation

Implemented in agent_tool.rs, the agent tool allows the agent to break down a hard query by creating a sub-agent.

  • Spawns a new Rig agent with identical toolkits and rules.
  • Appends depth markers to the system prompt: You are a sub-agent at recursion depth {n}.
  • Increments a shared atomic depth_counter.
  • If the depth hits MAX_DEPTH_WARNING (5), it issues a system warning log to prevent infinite loops.
  • When the sub-agent returns its result, the parent tool decrements the counter and passes the result back up.

SQLite Tracing & Project Isolation

Defined in trace.rs, three SQL tables exist:

  1. sessions: Tracks session start, end, LLM model name, and the folder from which the session was launched (project_path).
  2. turns: Tracks individual user questions, LLM responses, timestamps, token usage, session references, and a 1024-dim vector embedding (via qwen/qwen3-embedding-8b).
  3. tool_calls: Tracks what tools were called, their raw arguments, outputs, timestamps, and whether they succeeded.

All queries (search, recent history, tool history) are strictly isolated to the current project's directory (project_path) at the database query level to ensure security and clean contexts across different repositories.

Background Embedding Pipeline

After each turn, a background tokio task asynchronously generates a 1024-dim vector embedding of the prompt+response pair and stores it in the turns.embedding column. This enables the trace_search tool's semantic action to perform cosine-similarity search over past conversations without blocking the REPL response.

CLI Output Format

The REPL renders a structured, colorized UI using ANSI escape codes:

  ╭─ You ──────────────────────────────────────────────     ← user prompt box
  β”‚  <user input>
  ╰────────────────────────────────────────────────────

  βš™  <tool_name>      <args_json>                           ← tool call (parsed from Rig)
     ↳  <result_preview>                                     ← tool result (first line + line count)

  βš™  <tool>  <inline progress>                              ← tool progress (via interrupt_spinner)
  βœ“  <tool>  <inline progress>

  ╭─ Agent ────────────────────────────────────────────     ← agent response box
  β”‚  <agent output>
  ╰────────────────────────────────────────────────────
    ↳ <ms>ms Β· <in> in Β· <out> out                           ← token/time stats

  ╭─ background ───────────────────────────────────────     ← background tasks (queued)
  β”‚  ⟳ embedding turn #N via qwen3-8b…
  β”‚  βœ“ embedded turn #N (1024 dims)
  ╰────────────────────────────────────────────────────
  • Tool calls (βš™): Parsed from Rig's tracing::info!("executed tool ...") events. Displayed inline between prompt and response via CliLayer.
  • Tool progress: Tools with long-running steps (e.g. semantic search embedding) call cli::interrupt_spinner() / cli::restore_spinner() to print progress inline during execution.
  • Background tasks: Emitted via tracing::info!(target: "bg_task", ...). Queued in BG_QUEUE and flushed after the response to avoid stdout races with stdin.

πŸ›‘ Safety & Sandboxing Details

To prevent agents from performing destructive commands (like deletion or directory escapes), the project enforces safety boundaries across two layers:

  1. Filesystem Safety (check_filesystem_safety in hooks.rs):
    • Secondary defence: blocks paths that obviously start with .. before any I/O is attempted.
    • Primary defence: FilesystemTool::sandbox_path in filesystem.rs resolves and normalises the full path, then uses component-aware Path::starts_with to ensure the result stays within the current working directory (PWD). This catches embedded traversals like src/../../etc/passwd even if they don't start with ...
  2. Code Execution Safety (check_code_exec_safety):
    • Blocks execution of known dangerous shell sequences:
      • PowerShell: Remove-Item
      • Unix: rm -rf
      • Windows cmd: del /f
      • System level: Format-Volume
    • Commands are run in PowerShell (powershell -NoProfile -Command ...).

πŸ€– Guide for AI Agents (How to Read & Extend)

Hello, fellow AI! Here is how you can quickly work with this codebase:

Where to add new tools?

  1. Create a new source file in src/tools/.
  2. Define your arguments struct with #[derive(Deserialize, Serialize)] and your output struct.
  3. Implement rig::tool::Tool for your struct.
  4. Add the module declaration to src/tools/mod.rs.
  5. Register the tool in src/agent.rs using .tool(YourNewTool::new(ctx.clone())) inside the build_agent factory function.

How are tool calls tracked in traces?

  • Your tool should accept ToolContext in its constructor.
  • During tool execution (call), get the active turn ID via self.ctx.current_turn_id().
  • Use self.ctx.store.record_tool_call to insert a row in the database. Look at code_exec.rs for a clean example.

Key Variables & Defaults

  • Default LLM: deepseek/deepseek-v4-flash via OpenRouter (defined in src/agent.rs).
  • Global Config Dir: ~/.rust-agent/ (defined in src/config.rs via config_dir()).
  • Filesystem Sandbox: Current working directory (PWD) β€” wherever rust-agent is launched from.
  • Database File: ~/.rust-agent/traces.db (path computed by trace::db_path()).
  • Rules File: ~/.rust-agent/rules.md (loaded by rules::load_rules()).
  • Prompt Templates: ~/.rust-agent/prompts/ (loaded by tools/prompt.rs TEMPLATES static).

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages