Crustly Architecture Documentation
July 7, 2026 Β· View on GitHub
___ _ _
/ __|_ _ _ _ __| |_| |_ _
| (__| '_| || (_-< _| | || |
\___|_| \_,_/__/\__|_|\_, |
|__/
π₯ Flaky & Fast
by Jeremy JEANNE
Executive Summary
Crustly is a high-performance terminal AI assistant built in Rust, featuring:
- Multi-LLM Support: Anthropic, OpenAI, and local LLMs
- Extensible Tool System: 21 tools for file operations, code execution, agent delegation, and workflows
- Interactive TUI: Ratatui-based terminal interface with plan mode
- Local-First Storage: SQLite database for privacy and persistence
- Intelligent Prompt Analysis: Automatic tool hint detection
GitHub-renderable Mermaid versions of the diagrams below live in
docs/architecture/(C4 context/container), and an AI-queryable knowledge graph of the actual codebase lives indocs/graph/.
Table of Contents
- System Overview
- Module Architecture
- Core Components
- Data Flow
- Tool System
- Database Layer
- Service Layer
- Configuration
- Error Handling
- Design Patterns
- Class Diagrams
1. System Overview
High-Level Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β USER INTERFACE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β TUI (Ratatui) β CLI (Clap) β
β - Interactive chat β - Single commands β
β - Plan mode β - Batch processing β
β - File picker β - Configuration β
β - Tool approval β - Log management β
βββββββββββββββββββββββ¬βββββββββ΄βββββββββββ¬ββββββββββββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β APPLICATION LAYER β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β AgentService β PromptAnalyzer β
β - Conversation management β - Keyword detection β
β - Tool execution loop β - Tool hint injection β
β - Cost tracking β - Intent recognition β
βββββββββββββββββββββββ¬βββββββββ΄βββββββββββ¬ββββββββββββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PROVIDER LAYER β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Provider Trait β Tool Registry β
β ββ AnthropicProvider β ββ ReadTool / WriteTool β
β ββ OpenAIProvider β ββ EditTool / BashTool β
β ββ GeminiProvider β ββ GlobTool / GrepTool β
β ββ BedrockProvider β ββ LsTool / WebSearchTool β
β ββ AzureProvider β ββ CodeExecTool β
β ββ VertexAIProvider β ββ NotebookEditTool β
β β ββ DocParserTool β
β β ββ PlanTool / TaskTool β
β β ββ ContextTool β
β β ββ HttpClientTool β
β β ββ WebFetchTool β
β β ββ TodoWriteTool β
β β ββ AskUserTool β
β β ββ SkillTool β
β β ββ AgentTool β
β β ββ PowerShellTool β
βββββββββββββββββββββββ¬βββββββββ΄βββββββββββ¬ββββββββββββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SERVICE LAYER β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SessionService β MessageService β PlanService β
β - CRUD sessions β - CRUD messages β - Plan management β
β - Token tracking β - Conversation β - Task orchestration β
β - Cost aggregation β - History β - Execution workflow β
βββββββββββββββββββββββ¬βββββββββ΄βββββββββββ΄ββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DATABASE LAYER β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SQLite Database β
β ββ Sessions (chat history metadata) β
β ββ Messages (conversation content) β
β ββ Plans (structured task plans) β
β ββ PlanTasks (individual steps) β
β ββ Files (cached file contents) β
β ββ ToolExecutions (audit trail) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Key Characteristics
| Aspect | Description |
|---|---|
| Language | Rust 2021 Edition (1.75+) |
| Async Runtime | Tokio (full features) |
| TUI Framework | Ratatui + Crossterm |
| Database | SQLite via SQLx |
| HTTP Client | Reqwest |
| Configuration | TOML-based |
| License | FSL-1.1-MIT |
2. Module Architecture
Module Hierarchy
src/
βββ lib.rs # Library entry point & module declarations
βββ main.rs # Binary entry point
βββ error.rs # Global error types
β
βββ app/ # Application lifecycle
β βββ mod.rs
β
βββ cli/ # Command-line interface
β βββ mod.rs # CLI parsing, commands, handlers
β
βββ config/ # Configuration management
β βββ mod.rs # Config struct, loading, validation
β βββ secrets.rs # Secret management (API keys)
β βββ crabrace.rs # Crabrace provider registry
β βββ update.rs # Configuration updater
β
βββ db/ # Database layer
β βββ mod.rs # Database connection, pool
β βββ models.rs # Data models (Session, Message, Plan)
β βββ retry.rs # Retry logic for DB operations
β βββ repository/ # Repository pattern implementation
β βββ mod.rs
β βββ session.rs # SessionRepository
β βββ message.rs # MessageRepository
β βββ file.rs # FileRepository
β βββ plan.rs # PlanRepository
β
βββ llm/ # LLM abstraction layer
β βββ mod.rs
β βββ agent/ # Agent service
β β βββ service.rs # AgentService (core logic)
β β βββ context.rs # Conversation context management
β β βββ error.rs # Agent-specific errors
β βββ provider/ # LLM provider abstraction
β β βββ trait.rs # Provider trait definition
β β βββ types.rs # LLM request/response types
β β βββ anthropic.rs # Anthropic Claude provider
β β βββ openai.rs # OpenAI/Local LLM provider
β β βββ error.rs # Provider errors
β β βββ retry.rs # Retry logic
β βββ tools/ # Tool system
β β βββ mod.rs # Tool module exports
β β βββ trait.rs # Tool trait definition
β β βββ registry.rs # ToolRegistry
β β βββ error.rs # Tool errors
β β βββ [21 tool implementations...]
β βββ prompt/ # Prompt formatting
β βββ mod.rs
β
βββ logging.rs # Conditional debug logging
β
βββ services/ # Business logic layer
β βββ mod.rs # ServiceContext, ServiceManager
β βββ session.rs # SessionService
β βββ message.rs # MessageService
β βββ file.rs # FileService
β βββ plan.rs # PlanService
β
βββ tui/ # Terminal user interface
β βββ mod.rs
β βββ app.rs # App state management
β βββ runner.rs # TUI event loop
β βββ render.rs # Rendering logic
β βββ events.rs # Event handling
β βββ prompt_analyzer.rs # Keyword detection & hints
β βββ plan.rs # Plan document structure
β βββ splash.rs # Splash screen
β βββ highlight.rs # Syntax highlighting
β βββ markdown.rs # Markdown rendering
β βββ styles/ # UI styling
β βββ components/ # Reusable UI components
β βββ pages/ # UI pages
β βββ utils/ # TUI utilities
β
βββ events/ # Global event definitions
β βββ mod.rs
β
βββ message/ # Message types
β βββ mod.rs
β
βββ lsp/ # Language Server Protocol
β βββ mod.rs
β
βββ mcp/ # Model Context Protocol
β βββ mod.rs
β
βββ sync/ # Synchronization utilities
β βββ mod.rs
β
βββ utils/ # Utility functions
βββ mod.rs
Module Dependencies
main.rs
βββΊ cli::run()
βββΊ tui::run() / cmd_*()
βββΊ App::new()
βββΊ AgentService
β βββΊ Provider (trait)
β β βββΊ AnthropicProvider
β β βββΊ OpenAIProvider
β βββΊ ToolRegistry
β β βββΊ Tool (trait) x 21
β βββΊ ServiceContext
β βββΊ Database::pool()
βββΊ SessionService
βββΊ MessageService
βββΊ PlanService
βββΊ PromptAnalyzer
3. Core Components
3.1 AgentService
The central orchestrator for AI conversations.
pub struct AgentService {
provider: Arc<dyn Provider>, // LLM provider
context: ServiceContext, // Database access
tool_registry: Arc<ToolRegistry>, // Available tools
max_tool_iterations: usize, // Loop protection (default: 10)
default_system_prompt: Option<String>, // System prompt
auto_approve_tools: bool, // Skip approval dialogs
approval_callback: Option<ApprovalCallback>,
working_directory: PathBuf, // Tool execution directory
}
Key Methods:
| Method | Purpose |
|---|---|
send_message() | Simple message without tools |
send_message_with_tools() | Message with tool execution |
send_message_with_tools_and_mode() | With read-only mode support (non-streaming) |
send_message_with_tools_and_mode_streaming() | Streaming variant β forwards text chunks via UnboundedSender<String> |
Tool Execution Loop:
βββββββββββββββββββ
β User Message β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Send to LLM β
β (with tools) β
ββββββββββ¬βββββββββ
β
βΌ
ββββββββββββββ
β Tool Use? βββNOβββΊ Return Response
ββββββ¬ββββββββ
βYES
βΌ
βββββββββββββββββββ
β Approval Check β
βββββββββββββββββββ€
β requires_approval? β
β auto_approve? β
β user callback? β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Execute Tool β
β via Registry β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Add Result to β
β Conversation β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Back to LLM β
β (iteration++) β
ββββββββββ¬βββββββββ
β
(max 10 iterations)
3.2 Provider Abstraction
Unified interface for all LLM providers.
pub trait Provider: Send + Sync {
async fn complete(&self, request: LLMRequest) -> Result<LLMResponse>;
async fn stream(&self, request: LLMRequest) -> Result<ProviderStream>;
fn supports_streaming(&self) -> bool;
fn supports_tools(&self) -> bool;
fn supports_vision(&self) -> bool;
fn name(&self) -> &str;
fn default_model(&self) -> &str;
fn supported_models(&self) -> Vec<String>;
fn validate_model(&self, model: &str) -> bool;
fn context_window(&self, model: &str) -> Option<u32>;
fn calculate_cost(&self, model: &str, input: u32, output: u32) -> f64;
}
Implementations:
| Provider | Key Features |
|---|---|
| AnthropicProvider | Claude models, tool use, vision |
| OpenAIProvider | GPT models, local LLMs (Ollama, LM Studio) |
| GeminiProvider | Google Gemini models |
| BedrockProvider | AWS Bedrock |
| AzureProvider | Azure OpenAI Service |
| VertexAIProvider | Google Vertex AI |
3.3 TUI Application
Interactive terminal interface managing user experience.
pub struct App {
// Core state
current_session: Option<Session>,
messages: Vec<DisplayMessage>,
mode: AppMode,
// Processing
is_processing: bool,
streaming_response: Option<String>, // Live text accumulator (cleared on ResponseComplete)
// Reasoning / thinking
// Populated by complete_response() from AgentResponse.thinking_text
// Rendered as collapsible [Thinking βΈ/βΎ] block; toggled with 't'
// Plan mode
current_plan: Option<PlanDocument>,
executing_plan: bool,
// Tool approval
pending_approval: Option<ToolApprovalRequest>,
// Services
agent_service: Arc<AgentService>,
prompt_analyzer: PromptAnalyzer,
}
Application Modes:
| Mode | Purpose | Key Actions |
|---|---|---|
Splash | Startup screen | Wait 3s or press any key |
Chat | Main conversation | Send messages, use tools |
Plan | Plan review (read-only) | Approve/Reject/Revise plans |
Sessions | Session management | Switch/Create sessions |
ToolApproval | Permission dialog | Approve/Deny tool execution |
FilePicker | File selection | Browse and select files |
Help | Help screen | View keyboard shortcuts |
Settings | Configuration | Modify settings |
Keyboard Shortcuts:
| Shortcut | Action |
|---|---|
Ctrl+Enter | Submit message |
Ctrl+C | Quit |
Ctrl+N | New session |
Ctrl+L | List sessions |
Ctrl+P | Toggle plan mode |
Ctrl+A | Approve plan (Plan mode) |
Ctrl+R | Reject plan (Plan mode) |
Ctrl+I | Request revision (Plan mode) |
t | Toggle thinking panel on focused message |
@ | Open file picker |
Esc | Cancel/Back |
3.4 PromptAnalyzer
Automatically detects user intent and adds tool hints.
pub struct PromptAnalyzer {
plan_regex: Regex,
read_file_regex: Regex,
search_regex: Regex,
write_file_regex: Regex,
edit_file_regex: Regex,
bash_regex: Regex,
web_search_regex: Regex,
}
Keyword Detection:
| Tool | Example Keywords |
|---|---|
| plan | "make a plan", "create a plan", "plan for" |
| read_file | "read file", "show me file", "view file" |
| grep | "search for", "find", "grep", "locate" |
| write_file | "create file", "write file", "new file" |
| edit_file | "edit file", "modify file", "update file" |
| bash | "run command", "execute command", "shell command" |
| web_search | "search online", "google", "search the web" |
Example Transformation:
Input: "make a plan for implementing JWT authentication"
Output: "make a plan for implementing JWT authentication
**TOOL HINT**: Use the `plan` tool to create a structured plan with
tasks, dependencies, and implementation steps."
4. Data Flow
4.1 User Message Flow
User Types Message (TUI)
β
βΌ
βββββββββββββββββββββββ
β App.handle_chat_key β
β - Collect input β
β - Detect Ctrl+Enter β
βββββββββββ¬ββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β App.send_message() β
β - Analyze prompt β
β - Create chunk_tx β β unbounded_channel for streaming
β - Spawn forwarder β β task: chunk_rx β ResponseChunk events
βββββββββββ¬ββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β AgentService β
β .send_message_with_tools_and_ β
β mode_streaming(chunk_tx) β
β - Load conversation context β
β - Build LLMRequest with tools β
β - Call provider.stream() β
β - drain_stream_to_response() β
β ββ TextDelta β route_text_delta()
β β ββ outside <think> β text_buf + chunk_tx.send()
β β ββ inside <think> β thinking_buf (suppressed)
β ββ ThinkingDelta β thinking_buf (Anthropic)
β ββ ToolUse events β pending_tool / tool_uses
βββββββββββ¬ββββββββββββββββββββββββ
β
βΌ (streaming)
βββββββββββββββββββββββ
β TUI Event Loop β
β ResponseChunk(str) β β forwarded by forwarder task
β - append_streaming_ β
β chunk() β render β β live [streaming] label in UI
βββββββββββ¬ββββββββββββ
β (on stream end, forwarder exits)
βΌ
βββββββββββββββββββββββββββββββββββ
β AgentService: Tool Execution β
β (if tool_use block present) β
β - Approval check β
β - Execute tool via Registry β
β - Format result, continue loop β
βββββββββββ¬ββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β Save to Database β
β - Message content β
β - Token usage β
β - Cost calculation β
βββββββββββ¬ββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β ResponseComplete β
β (AgentResponse) β
β - content blocks β
β - thinking_text β β extracted from ContentBlock::Thinking
β - usage / cost β
βββββββββββ¬ββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β complete_response() β
β - clears streaming_ β
β response β
β - adds DisplayMsg β
β with thinking_textβ
βββββββββββββββββββββββ
β - Message content β β - Token usage β β - Cost calculation β βββββββββββ¬ββββββββββββ β βΌ βββββββββββββββββββββββ β Return AgentResponseβ β - content β β - usage β β - cost β β - model β βββββββββββββββββββββββ
### 4.2 Plan Creation Flow
User: "make a plan for implementing login" β βΌ βββββββββββββββββββββββ β AgentService β β - LLM recognizes β β plan intent β βββββββββββ¬ββββββββββββ β βΌ βββββββββββββββββββββββ β PlanTool.execute() β β operation: "create" β β - Create PlanDoc β β - Save to DB β βββββββββββ¬ββββββββββββ β βΌ βββββββββββββββββββββββ β PlanTool.execute() β β operation: "add_task"β β - Add task 1 β β - Add task 2 β β - Add task N β βββββββββββ¬ββββββββββββ β βΌ βββββββββββββββββββββββ β PlanTool.execute() β β operation: "finalize"β β - Set status to β β PendingApproval β βββββββββββ¬ββββββββββββ β βΌ βββββββββββββββββββββββ β App.check_and_load_ β β plan() β β - Load plan from DB β β - Show notification β β - Wait for user β β approval β βββββββββββ¬ββββββββββββ β βββββββ΄ββββββ β User β β Action β βββββββ¬ββββββ β βββββββΌββββββ¬ββββββββββ β β β β βΌ βΌ βΌ βΌ Ctrl+A Ctrl+R Ctrl+I Esc Approve Reject Revise Cancel β β β β βΌ βΌ βΌ βΌ Execute Clear Prefill Return Plan Plan Input to Chat
### 4.3 Tool Approval Flow
βββββββββββββββββββββββ β Tool requires β β approval? β βββββββββββ¬ββββββββββββ β ββββββ΄βββββ β YES β ββββββ¬βββββ β βΌ βββββββββββββββββββββββ β context.auto_approveβ β enabled? β βββββββββββ¬ββββββββββββ β ββββββ΄βββββ ββββββ β NO β β YESβ ββββββ¬βββββ ββββ¬ββ β β βΌ βΌ βββββββββββββββββββ βββββββββββββββ β Create Approval β β Execute β β Request β β Immediately β β - tool_name β βββββββββββββββ β - tool_input β β - capabilities β βββββββββββ¬ββββββββ β βΌ βββββββββββββββββββββββ β Send to TUI via β β mpsc channel β βββββββββββ¬ββββββββββββ β βΌ βββββββββββββββββββββββ β TUI: ToolApproval β β Mode β β - Display tool info β β - Show capabilities β β - 5-minute timeout β βββββββββββ¬ββββββββββββ β ββββββ΄βββββ β User β β Input β ββββββ¬βββββ β βββββββΌββββββ βΌ βΌ βΌ 'A' 'D' Timeout Approve Deny (5min) β β β βΌ βΌ βΌ Execute Return Return Tool Error Error
---
## 4.4 Streaming Architecture
### Overview
Crustly streams LLM responses token-by-token to the TUI using a `tokio::sync::mpsc::unbounded_channel`:
AgentService (async task) TUI Event Loop ββββββββββββββββββββββββββββ βββββββββββββββββββββββββ β provider.stream(request) β β TuiEvent::ResponseChunkβ β β β β β append_streaming_ β β βΌ β chunk β chunk() β β drain_stream_to_response βββtxββββΆβ β streaming_response β β β β β rendered live β β ββ TextDelta (visible) β βββββββββββββββββββββββββ β β ββ tx.send(text) β β ββ ThinkingDelta / tagsβ βββββββββββββββββββββββββ β ββ thinking_buf β await β TuiEvent::ResponseCompβ β β fwd βββΆ lete(AgentResponse) β β await forwarder_handle β β β clear streaming_ β β send(ResponseComplete) β β response β ββββββββββββββββββββββββββββ β β show DisplayMessageβ βββββββββββββββββββββββββ
**Race-condition guarantee:** `forwarder_handle.await` is called before `ResponseComplete` is emitted. This ensures all `ResponseChunk` events are already in the TUI channel when `ResponseComplete` arrives β FIFO ordering is maintained.
### `drain_stream_to_response` (free async fn in `service.rs`)
Consumes a `ProviderStream` and assembles a complete `LLMResponse`:
| Stream event | Action |
|---|---|
| `MessageStart` | Capture response ID |
| `ContentBlockStart(ToolUse)` | Store as `pending_tool` |
| `ContentBlockStop` | Flush `pending_tool` β `tool_uses` |
| `ContentBlockDelta::TextDelta` | Route via `route_text_delta()` |
| `ContentBlockDelta::ThinkingDelta` | Append to `thinking_buf` |
| `MessageDelta` | Capture stop reason + token usage |
| `MessageStop` | Break loop |
| `StreamEvent::Error` | Return hard error immediately |
### `route_text_delta` (private fn in `service.rs`)
Statefully routes each `TextDelta` through `<think>` tag detection:
TextDelta text
β
ββ outside
State: in_think_block: bool persists between delta calls
**Post-processing fallback:** if `thinking_buf` is empty after the stream ends (no `ThinkingDelta` events and no `<think>` tags detected in-stream), `extract_think_tags()` is run on the assembled `text_buf` as a safety net.
---
## 4.5 Reasoning / Thinking Display
### Sources
Crustly unifies three reasoning sources into a single `ContentBlock::Thinking`:
| Source | Provider | Field / Mechanism |
|--------|----------|-------------------|
| Anthropic extended thinking | AnthropicProvider | `ThinkingDelta` stream events |
| DeepSeek-R1 direct API | OpenAIProvider | `reasoning_content` JSON field |
| Ollama tag-based reasoning | OpenAIProvider | `<think>β¦</think>` in content text |
### `extract_think_tags(text: &str) -> (String, String)` (`types.rs`)
Utility that strips all `<think>β¦</think>` blocks from text:
- Returns `(thinking_content, cleaned_text)` β both trimmed
- Handles multiple blocks (joined with `\n`)
- Unclosed `<think>` tag: rest of string treated as thinking
- Case-sensitive (`<think>` only β consistent with DeepSeek/QwQ output)
### Priority logic in `from_openai_response()` (`openai.rs`)
- reasoning_content field present & non-empty? YES β use it as thinking; preserve content text verbatim (no tag stripping) NO β run extract_think_tags() on content text; tag_thinking β thinking, cleaned β visible text
### TUI rendering (`render.rs`)
DisplayMessage.thinking_text: Option
---
## 5. Tool System
### 5.1 Tool Trait
```rust
#[async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn input_schema(&self) -> Value; // JSON Schema
fn capabilities(&self) -> Vec<ToolCapability>;
fn requires_approval(&self) -> bool;
async fn execute(&self, input: Value, context: &ToolExecutionContext)
-> Result<ToolResult>;
fn validate_input(&self, _input: &Value) -> Result<()>;
}
5.2 Tool Capabilities
pub enum ToolCapability {
ReadFiles, // Can read file contents
WriteFiles, // Can modify/create files
ExecuteShell, // Can run shell commands
Network, // Can access network
SystemModification, // Can modify system state
PlanManagement, // Can manage plans/tasks
}
5.3 All 21 Tools
| # | Tool Name | File | Capabilities | Approval | Description |
|---|---|---|---|---|---|
| 1 | read_file | read.rs | ReadFiles | No | Read file contents with line ranges |
| 2 | write_file | write.rs | WriteFiles, SystemMod | Yes | Create or overwrite files |
| 3 | edit_file | edit.rs | WriteFiles, SystemMod | Yes | Edit files (replace, insert, delete, regex) |
| 4 | bash | bash.rs | ExecuteShell, SystemMod | Yes | Execute shell commands |
| 5 | ls | ls.rs | ReadFiles | No | List directory contents |
| 6 | glob | glob.rs | ReadFiles | No | Find files by pattern |
| 7 | grep | grep.rs | ReadFiles | No | Search file contents (literal/regex) |
| 8 | web_search | web_search.rs | Network | No | Internet search (DuckDuckGo) |
| 9 | execute_code | code_exec.rs | ExecuteShell, SystemMod | Yes | Run Python/JS/Rust/Shell code |
| 10 | notebook_edit | notebook.rs | WriteFiles, SystemMod | Yes | Edit Jupyter notebooks |
| 11 | parse_document | doc_parser.rs | ReadFiles | No | Extract text from PDF/DOCX documents |
| 12 | task | task.rs | PlanManagement | No | Task tracking and management |
| 13 | context | context.rs | PlanManagement | No | Session context/variables |
| 14 | http_request | http.rs | Network | No | Make HTTP API requests |
| 15 | plan | plan_tool.rs | PlanManagement | No | Create and manage structured plans |
| 16 | web_fetch | web_fetch.rs | Network | No | Fetch a URL and extract readable text |
| 17 | todo_write | todo_write.rs | WriteFiles | No | Read/write persistent todo lists |
| 18 | ask_user | ask_user.rs | β | No | Pause execution and ask the user a question |
| 19 | skill | skill.rs | ReadFiles | No | Load a named skill (slash command) from SKILL.md |
| 20 | agent | agent.rs | WriteFiles | No | Spawn a background sub-agent for a focused task |
| 21 | powershell | powershell.rs | ExecuteShell, SystemMod, Network | Yes | Execute PowerShell (pwsh / powershell.exe) commands |
5.4 Tool Execution Context
pub struct ToolExecutionContext {
pub session_id: Uuid,
pub working_directory: PathBuf,
pub env_vars: HashMap<String, String>,
pub auto_approve: bool,
pub timeout_secs: u64,
pub read_only_mode: bool, // Plan mode restriction
pub sub_agent_launcher: Option<Arc<dyn SubAgentLauncher>>, // Injected by AgentService
}
SubAgentLauncher is a trait injected into the context by AgentService. AgentTool depends on it to spawn sub-agents without knowing AgentService internals. Sub-agents created via the launcher have allow_sub_agents: false to prevent infinite recursion.
Read-Only Mode Restrictions:
When read_only_mode = true:
- β
write_file: Blocked - β
edit_file: Blocked - β
execute_code: Blocked - β
notebook_edit: Blocked - β
agent: Blocked (cannot spawn sub-agents in plan mode) - β οΈ
bash: Filters unsafe commands (>, >>, | tee, rm, mv, etc.) - β οΈ
powershell: Allowlist of safe cmdlets (Get-Content, Select-String, etc.); blocks redirection, Remove-Item, Invoke-Expression, etc. - β All other tools: Normal operation
5.5 ToolRegistry
pub struct ToolRegistry {
tools: HashMap<String, Arc<dyn Tool>>,
}
impl ToolRegistry {
pub fn new() -> Self;
pub fn register(&mut self, tool: Arc<dyn Tool>);
pub fn get(&self, name: &str) -> Option<Arc<dyn Tool>>;
pub fn has_tool(&self, name: &str) -> bool;
pub fn list_tools(&self) -> Vec<String>;
pub fn get_tool_definitions(&self) -> Vec<Tool>; // For LLM
pub async fn execute(&self, name: &str, input: Value,
context: &ToolExecutionContext) -> Result<ToolResult>;
pub fn count(&self) -> usize;
}
Registration in CLI:
let mut tool_registry = ToolRegistry::new();
// Phase 1: Essential file operations
tool_registry.register(Arc::new(ReadTool));
tool_registry.register(Arc::new(WriteTool));
tool_registry.register(Arc::new(EditTool));
tool_registry.register(Arc::new(BashTool));
tool_registry.register(Arc::new(LsTool));
tool_registry.register(Arc::new(GlobTool));
tool_registry.register(Arc::new(GrepTool));
// Phase 2: Advanced features
tool_registry.register(Arc::new(WebSearchTool));
tool_registry.register(Arc::new(CodeExecTool));
tool_registry.register(Arc::new(NotebookEditTool));
tool_registry.register(Arc::new(DocParserTool));
// Phase 3: Workflow & integration
tool_registry.register(Arc::new(TaskTool));
tool_registry.register(Arc::new(ContextTool));
tool_registry.register(Arc::new(HttpClientTool));
tool_registry.register(Arc::new(PlanTool));
// Phase 4: Claw Code parity
tool_registry.register(Arc::new(WebFetchTool));
tool_registry.register(Arc::new(TodoWriteTool));
tool_registry.register(Arc::new(AskUserTool));
tool_registry.register(Arc::new(SkillTool));
tool_registry.register(Arc::new(AgentTool));
tool_registry.register(Arc::new(PowerShellTool));
6. Database Layer
6.1 Connection Management
pub struct Database {
pool: SqlitePool,
}
impl Database {
pub async fn connect<P: AsRef<Path>>(path: P) -> Result<Self>;
pub async fn connect_in_memory() -> Result<Self>;
pub fn pool(&self) -> &SqlitePool;
pub fn is_connected(&self) -> bool;
pub async fn run_migrations(&self) -> Result<()>;
pub async fn close(self) -> Result<()>;
}
Connection Configuration:
- Max connections: 5
- Busy timeout: 5 seconds
- WAL mode for concurrency
- SQLx migrations
6.2 Data Models
Session:
pub struct Session {
pub id: Uuid,
pub title: Option<String>,
pub model: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub archived_at: Option<DateTime<Utc>>,
pub token_count: i32,
pub total_cost: f64,
}
Message:
pub struct Message {
pub id: Uuid,
pub session_id: Uuid,
pub role: String,
pub content: String,
pub sequence: i32,
pub created_at: DateTime<Utc>,
pub token_count: Option<i32>,
pub cost: Option<f64>,
}
Plan:
pub struct Plan {
pub id: Uuid,
pub session_id: Uuid,
pub title: String,
pub description: String,
pub context: String,
pub risks: String, // JSON array
pub test_strategy: String,
pub technical_stack: String, // JSON array
pub status: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub approved_at: Option<DateTime<Utc>>,
}
PlanTask:
pub struct PlanTask {
pub id: Uuid,
pub plan_id: Uuid,
pub task_order: i32,
pub title: String,
pub description: String,
pub task_type: String,
pub dependencies: String, // JSON array
pub complexity: i32,
pub acceptance_criteria: String,
pub status: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
6.3 Repository Pattern
Service Layer
β
βΌ
Repository (trait)
β
βββΊ SessionRepository
β - create(session)
β - find_by_id(id)
β - list(options)
β - update(session)
β - delete(id)
β
βββΊ MessageRepository
β - create(message)
β - find_by_session(session_id)
β - find_by_id(id)
β - update(message)
β - delete_by_session(session_id)
β
βββΊ PlanRepository
- create(plan)
- find_by_id(id)
- find_by_session(session_id)
- update(plan)
- delete(id)
7. Service Layer
7.1 ServiceContext
Shared dependency injection container.
pub struct ServiceContext {
pub pool: Arc<Pool>,
}
impl ServiceContext {
pub fn new(pool: Arc<Pool>) -> Self;
pub fn clone(&self) -> Self;
}
7.2 Services
SessionService:
- Session CRUD operations
- Token and cost tracking
- Archive management
MessageService:
- Message CRUD operations
- Conversation history management
- Usage tracking per message
PlanService:
- Plan lifecycle management
- JSON import/export
- Status transitions
FileService:
- Cached file operations
- Content versioning
- File metadata
7.3 Service Pattern
// Example: SessionService
pub struct SessionService {
context: ServiceContext,
}
impl SessionService {
pub fn new(context: ServiceContext) -> Self {
Self { context }
}
pub async fn create_session(&self, title: Option<String>) -> Result<Session> {
let session = Session {
id: Uuid::new_v4(),
title,
model: None,
created_at: Utc::now(),
updated_at: Utc::now(),
archived_at: None,
token_count: 0,
total_cost: 0.0,
};
SessionRepository::create(self.context.pool(), &session).await?;
Ok(session)
}
// ... other methods
}
8. Configuration
8.1 Config Structure
pub struct Config {
pub crabrace: CrabraceConfig,
pub database: DatabaseConfig,
pub logging: LoggingConfig,
pub debug: DebugConfig,
pub providers: ProviderConfigs,
}
pub struct ProviderConfigs {
pub anthropic: Option<ProviderConfig>,
pub openai: Option<ProviderConfig>,
pub gemini: Option<ProviderConfig>,
pub bedrock: Option<ProviderConfig>,
pub azure: Option<ProviderConfig>,
pub vertex: Option<ProviderConfig>,
}
pub struct ProviderConfig {
pub enabled: bool,
pub api_key: Option<String>,
pub base_url: Option<String>,
pub default_model: Option<String>,
}
pub struct DatabaseConfig {
pub path: PathBuf,
pub max_connections: u32,
pub busy_timeout_secs: u64,
}
8.2 Loading Priority
- Default configuration
- Config file (
~/.config/crustly/config.toml) - Environment variables
- CLI arguments
8.3 Environment Variables
| Variable | Description |
|---|---|
ANTHROPIC_API_KEY | Anthropic Claude API key |
OPENAI_API_KEY | OpenAI API key |
OPENAI_BASE_URL | Custom OpenAI-compatible endpoint |
CRUSTLY_CONFIG | Custom config file path |
RUST_LOG | Log level filter |
9. Error Handling
9.1 Error Hierarchy
pub enum CrustlyError {
Database(sqlx::Error),
Io(std::io::Error),
Config { message: String, code: ErrorCode },
Provider { provider: String, message: String, code: ErrorCode },
ToolExecution { tool: String, message: String, code: ErrorCode },
PermissionDenied(String),
}
pub enum ErrorCode {
// Configuration (1000-1999)
ConfigNotFound = 1000,
ConfigInvalid = 1001,
ConfigMergeError = 1002,
// Provider (2000-2999)
ProviderNotFound = 2000,
ProviderAuthFailed = 2001,
ProviderRateLimit = 2002,
ProviderTimeout = 2003,
// Tool (3000-3999)
ToolNotFound = 3000,
ToolExecutionFailed = 3001,
ToolTimeout = 3002,
// Permission (4000-4999)
PermissionDenied = 4000,
PermissionNotGranted = 4001,
}
9.2 Tool Errors
pub enum ToolError {
NotFound(String),
InvalidInput(String),
Execution(String),
ApprovalRequired(String),
Io(io::Error),
Timeout,
}
9.3 Error Propagation
Tool β ToolError
β
βΌ
ToolRegistry β Result<ToolResult>
β
βΌ
AgentService β AgentError
β
βΌ
App β TuiEvent::Error(String)
β
βΌ
User sees error message in UI
10. Design Patterns
10.1 Trait-Based Abstraction
- Provider Trait: Unified LLM interface
- Tool Trait: Extensible tool system
- Repository Pattern: Database abstraction
10.2 Builder Pattern
// AgentService configuration
AgentService::new(provider, context)
.with_system_prompt("...")
.with_tool_registry(registry)
.with_auto_approve_tools(false)
.with_approval_callback(Some(callback))
.with_max_tool_iterations(10)
.with_working_directory(dir)
// ToolExecutionContext
ToolExecutionContext::new(session_id)
.with_auto_approve(false)
.with_timeout(30)
.with_read_only_mode(false)
.with_working_directory(dir)
// LogConfig
LogConfig::new()
.with_debug_mode(true)
.with_log_level(Level::DEBUG)
.with_log_dir(path)
10.3 Registry Pattern
- ToolRegistry: Dynamic tool management
- Runtime registration and lookup
- Tool definition generation for LLM
10.4 Service Layer Pattern
- ServiceContext: Dependency injection
- ServiceManager: Facade
- Individual services (Session, Message, Plan, File)
10.5 Event-Driven Architecture
pub enum TuiEvent {
Key(KeyEvent),
MessageSubmitted(String),
ResponseChunk(String), // streaming: partial text token
ResponseComplete(AgentResponse),
ToolApprovalRequested(ToolApprovalRequest),
ToolApprovalResponse(ToolApprovalResponse),
// ...
}
// Event loop
loop {
match app.next_event().await {
Some(event) => app.handle_event(event).await?,
None => break,
}
}
10.6 Concurrency with Arc/Mutex
provider: Arc<dyn Provider>,
tool_registry: Arc<ToolRegistry>,
agent_service: Arc<AgentService>,
pool: Arc<SqlitePool>,
11. Class Diagrams
11.1 PlantUML Diagram
See docs/architecture.puml for the complete PlantUML class diagram.
To render:
# Install PlantUML
brew install plantuml # macOS
apt install plantuml # Linux
# Generate diagram
plantuml docs/architecture.puml
# Or use online renderer
# https://www.plantuml.com/plantuml/
11.2 Core Class Relationships
βββββββββββββββββββ βββββββββββββββββββ
β App β β AgentService β
βββββββββββββββββββ€ uses βββββββββββββββββββ€
β agent_service ββββββββββΆβ provider β
β prompt_analyzer β β tool_registry β
β session_service β β context β
β message_service β βββββββββββββββββββ
β plan_service β β
βββββββββββββββββββ βuses
βΌ
βββββββββββββββββββ
β ToolRegistry β
βββββββββββββββββββ€
β tools: HashMap β
β register() β
β execute() β
βββββββββββββββββββ
βmanages
βΌ
βββββββββββββββββββ
β Tool (trait) β
βββββββββββββββββββ€
β name() β
β execute() β
β capabilities() β
βββββββββββββββββββ
β³
βββββββββββββββββΌββββββββββββββββ
β β β
βββββββββ΄ββββββββ βββββββ΄ββββββ ββββββββ΄βββββββ
β ReadTool β β WriteTool β β ... β
βββββββββββββββββ βββββββββββββ βββββββββββββββ
11.3 Provider Abstraction
βββββββββββββββββββββββ
β Provider (trait) β
βββββββββββββββββββββββ€
β +complete() β
β +stream() β
β +calculate_cost() β
β +context_window() β
βββββββββββββββββββββββ
β³
βimplements
βββββββ΄ββββββ
β β
βββββ΄ββββ βββββ΄ββββ
βAnthropicβ βOpenAI β
βProvider β βProviderβ
βββββββββββ βββββββββββ
11.4 Database Layer
βββββββββββββββββββ
β ServiceContext β
βββββββββββββββββββ€
β pool: Arc<Pool> β
βββββββββββββββββββ
β
wrapsβ
βΌ
βββββββββββββββββββ
β Database β
βββββββββββββββββββ€
β pool: SqlitePoolβ
β connect() β
β run_migrations()β
βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ operates on βββββββββββββββ
β Repository ββββββββββββββββββββΆβ Model β
βββββββββββββββββββ€ βββββββββββββββ€
β create() β β Session β
β find_by_id() β β Message β
β update() β β Plan β
β delete() β β PlanTask β
βββββββββββββββββββ βββββββββββββββ
Appendix A: File Structure Summary
Total files: ~150+
Total lines of code: ~25,000+
Primary language: Rust (100%)
Dependencies: 652 crates
Appendix B: Performance Characteristics
| Operation | Expected Performance |
|---|---|
| Tool execution | <100ms (local tools) |
| LLM request | 1-30s (network dependent) |
| Database query | <10ms |
| TUI rendering | 60 FPS |
| Build time (debug) | ~90s |
| Binary size | ~50MB |
Appendix C: Security Considerations
- Tool Approval System: Dangerous operations require user consent
- Read-Only Mode: Plan mode restricts write operations
- API Key Management: Secure storage via environment variables
- Input Validation: All tool inputs validated before execution
- Command Filtering: Bash tool filters unsafe commands in read-only mode
Appendix D: Future Enhancements
- RAG (Retrieval-Augmented Generation) support
- Vector store integration
- More LLM providers (Gemini, Azure, Vertex)
- Plugin system for custom tools
- Web interface
- Multi-user support
- Enhanced LSP integration (currently a stub)
-
Real-time streaming TUI(implemented) -
Reasoning / thinking display(implemented β DeepSeek-R1, QwQ-32B, Anthropic)
Document Version: 1.2 Last Updated: May 2026 Author: Jeremy JEANNE License: FSL-1.1-MIT