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 in docs/graph/.


Table of Contents

  1. System Overview
  2. Module Architecture
  3. Core Components
  4. Data Flow
  5. Tool System
  6. Database Layer
  7. Service Layer
  8. Configuration
  9. Error Handling
  10. Design Patterns
  11. 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

AspectDescription
LanguageRust 2021 Edition (1.75+)
Async RuntimeTokio (full features)
TUI FrameworkRatatui + Crossterm
DatabaseSQLite via SQLx
HTTP ClientReqwest
ConfigurationTOML-based
LicenseFSL-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:

MethodPurpose
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:

ProviderKey Features
AnthropicProviderClaude models, tool use, vision
OpenAIProviderGPT models, local LLMs (Ollama, LM Studio)
GeminiProviderGoogle Gemini models
BedrockProviderAWS Bedrock
AzureProviderAzure OpenAI Service
VertexAIProviderGoogle 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:

ModePurposeKey Actions
SplashStartup screenWait 3s or press any key
ChatMain conversationSend messages, use tools
PlanPlan review (read-only)Approve/Reject/Revise plans
SessionsSession managementSwitch/Create sessions
ToolApprovalPermission dialogApprove/Deny tool execution
FilePickerFile selectionBrowse and select files
HelpHelp screenView keyboard shortcuts
SettingsConfigurationModify settings

Keyboard Shortcuts:

ShortcutAction
Ctrl+EnterSubmit message
Ctrl+CQuit
Ctrl+NNew session
Ctrl+LList sessions
Ctrl+PToggle plan mode
Ctrl+AApprove plan (Plan mode)
Ctrl+RReject plan (Plan mode)
Ctrl+IRequest revision (Plan mode)
tToggle thinking panel on focused message
@Open file picker
EscCancel/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:

ToolExample 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 block ──► text_buf + chunk_tx.send() (visible to TUI) └─ inside … β–Ί thinking_buf (hidden from TUI)

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`)

  1. 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 β”‚ press 't' to toggle thinking_expanded β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β” β”‚ collapsed β”‚ expanded β–Ό β–Ό [Thinking β–Έ] [Thinking β–Ύ] β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ thinking β”‚ β”‚ content β”‚ β”‚ rendered as β”‚ β”‚ markdown β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜


---

## 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 NameFileCapabilitiesApprovalDescription
1read_fileread.rsReadFilesNoRead file contents with line ranges
2write_filewrite.rsWriteFiles, SystemModYesCreate or overwrite files
3edit_fileedit.rsWriteFiles, SystemModYesEdit files (replace, insert, delete, regex)
4bashbash.rsExecuteShell, SystemModYesExecute shell commands
5lsls.rsReadFilesNoList directory contents
6globglob.rsReadFilesNoFind files by pattern
7grepgrep.rsReadFilesNoSearch file contents (literal/regex)
8web_searchweb_search.rsNetworkNoInternet search (DuckDuckGo)
9execute_codecode_exec.rsExecuteShell, SystemModYesRun Python/JS/Rust/Shell code
10notebook_editnotebook.rsWriteFiles, SystemModYesEdit Jupyter notebooks
11parse_documentdoc_parser.rsReadFilesNoExtract text from PDF/DOCX documents
12tasktask.rsPlanManagementNoTask tracking and management
13contextcontext.rsPlanManagementNoSession context/variables
14http_requesthttp.rsNetworkNoMake HTTP API requests
15planplan_tool.rsPlanManagementNoCreate and manage structured plans
16web_fetchweb_fetch.rsNetworkNoFetch a URL and extract readable text
17todo_writetodo_write.rsWriteFilesNoRead/write persistent todo lists
18ask_userask_user.rsβ€”NoPause execution and ask the user a question
19skillskill.rsReadFilesNoLoad a named skill (slash command) from SKILL.md
20agentagent.rsWriteFilesNoSpawn a background sub-agent for a focused task
21powershellpowershell.rsExecuteShell, SystemMod, NetworkYesExecute 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

  1. Default configuration
  2. Config file (~/.config/crustly/config.toml)
  3. Environment variables
  4. CLI arguments

8.3 Environment Variables

VariableDescription
ANTHROPIC_API_KEYAnthropic Claude API key
OPENAI_API_KEYOpenAI API key
OPENAI_BASE_URLCustom OpenAI-compatible endpoint
CRUSTLY_CONFIGCustom config file path
RUST_LOGLog 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

OperationExpected Performance
Tool execution<100ms (local tools)
LLM request1-30s (network dependent)
Database query<10ms
TUI rendering60 FPS
Build time (debug)~90s
Binary size~50MB

Appendix C: Security Considerations

  1. Tool Approval System: Dangerous operations require user consent
  2. Read-Only Mode: Plan mode restricts write operations
  3. API Key Management: Secure storage via environment variables
  4. Input Validation: All tool inputs validated before execution
  5. 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