Sofos is a terminal-based AI coding assistant powered by Anthropic's Claude API. It's built in Rust for maximum performance and security. The assistant can read/write files, search code, execute bash commands, and search the web - all within a sandboxed environment.
Core Philosophy:
- Security first: All operations are sandboxed to the workspace directory
- Fast and efficient: Native Rust implementation with optional ultra-fast editing via Morph API
- Developer-friendly: Interactive REPL with session persistence and custom instructions
- Transparent: All tool executions are visible to the user
-
Dual Session Storage Format (src/session/history.rs)
api_messages: Anthropic API format for continuing conversationsdisplay_messages: UI-friendly format for showing conversation history- This separation ensures Claude sees proper API format while users see original UI
-
Tool Calling Pattern (src/repl/mod.rs)
- Assistant returns content blocks (text + tool_use)
- REPL executes tools and collects results
- Results sent back as user message with tool_result blocks
- Loop-based handling allows Claude to use multiple tools in sequence iteratively
-
Two-Level Instructions (src/session/history.rs)
AGENTS.md: Project-level, version controlled.sofos/instructions.md: Personal, gitignored- Both appended to system prompt at startup
-
Sandboxing Strategy (src/tools/filesystem.rs, src/tools/bashexec.rs)
- All paths validated before operations
- Parent directory traversal blocked (
..) - Absolute paths rejected
- Symlinks checked to prevent escape
- Bash commands filtered through blocklist
src/
├── main.rs # Entry point
├── cli.rs # CLI argument parsing
├── error.rs # Error types
├── error_ext.rs # Error extensions
├── config.rs # Configuration (SofosConfig, ModelConfig)
│
├── api/ # API clients
│ ├── anthropic.rs # Claude API client
│ ├── openai.rs # OpenAI API client
│ ├── morph.rs # Morph Apply API client
│ ├── types.rs # Message types and serialization
│ └── utils.rs # API utilities
│
├── mcp/ # MCP (Model Context Protocol) integration
│ ├── mod.rs # MCP module exports
│ ├── config.rs # MCP server configuration loading
│ ├── protocol.rs # MCP protocol types (JSON-RPC, tools)
│ ├── client.rs # MCP client implementations (stdio, HTTP)
│ └── manager.rs # MCP server connection management
│
├── repl/ # REPL components
│ ├── mod.rs # Main REPL loop and Repl struct
│ ├── clipboard_edit_mode.rs # Custom EditMode wrapping Emacs to intercept Ctrl+V
│ ├── conversation.rs # Message history management
│ ├── prompt.rs # Prompt rendering
│ ├── request_builder.rs # API request construction
│ └── response_handler.rs # Response processing
│
├── session/ # Session management
│ ├── history.rs # Session persistence + custom instructions
│ ├── state.rs # Runtime session state
│ └── selector.rs # Session selection TUI
│
├── clipboard.rs # Clipboard image paste (Ctrl+V) with numbered markers
│
├── tools/ # Tool implementations
│ ├── filesystem.rs # File operations (read, write, list, etc)
│ ├── bashexec.rs # Sandboxed bash execution
│ ├── codesearch.rs # Ripgrep integration
│ ├── image.rs # Image handling (local paths, URLs)
│ ├── permissions.rs # 3-tier command permission system
│ ├── tool_name.rs # Type-safe tool name enum
│ ├── types.rs # Tool definitions for API
│ └── utils.rs # Tool utilities (confirmations, HTML-to-text)
│
├── ui/ # UI components
│ ├── mod.rs # Main UI utilities and display logic
│ ├── syntax.rs # Markdown/code syntax highlighting
│ └── diff.rs # Contextual diff generation and display
│
└── commands/ # Built-in commands
└── builtin.rs # Command implementations
src/api/types.rs
- Defines Message, ContentBlock, and MessageContentBlock enums
- Handles serialization/deserialization for Anthropic API
- Supports both regular and server-side tools (like web_search)
src/ui/diff.rs
- Generate contextual diffs with syntax highlighting and line numbers
- Uses
similarcrate for diffing,syntectfor syntax coloring - Dark backgrounds (#5e0000 deletions, #00005f additions) with syntax-colored code
- Context lines (default: 2) show unchanged code around changes
- Used by edit_file, write_file, and morph_edit_file tools
src/session/history.rs
- SessionMetadata: Preview and timestamps for session list
- Session: Dual storage (api_messages + display_messages)
- DisplayMessage: Enum for user messages, assistant responses, and tool executions
src/repl/mod.rs
- Main event loop (run method)
- Manages REPL state and user interaction
- Coordinates conversation, tools, and UI
src/repl/response_handler.rs
- Iteratively processes assistant responses and tool calls
- Max iterations: 200 (prevents infinite loops)
src/repl/conversation.rs
- Manages in-memory message history
- Trims to MAX_MESSAGES (500) to prevent token overflow
- Builds system prompt with features list and custom instructions
src/tools/filesystem.rs
- validate_path: Security-critical path validation
- All operations check sandboxing before execution
- File size limit: 10MB to prevent memory issues
- User confirmation required for deletions
src/tools/bashexec.rs
- Uses PermissionManager for 3-tier command checking
- Validates command structure (paths, redirection, git ops)
- Executes commands with size limits
- Provides detailed rejection messages
src/tools/permissions.rs
- PermissionManager: Manages command permission checking
- PermissionSettings: Stores user's allow/deny/ask lists
- Three permission tiers: Allowed, Denied, Ask
- Persists decisions to
.sofos/config.local.toml - Predefined lists: ~50 allowed commands, ~30 forbidden commands
- Wildcard matching:
Bash(cargo:*)matches all cargo commands - Exact matching:
Bash(cargo build)matches specific command only
- Follow standard Rust idioms and conventions
- Use meaningful variable names (no single-letter except in loops)
- Prefer
Result<T>overpanic!for error handling - Use
?operator for error propagation - Keep functions focused and under ~100 lines
- Custom
SofosErrorenum in src/error.rs - Always provide context in error messages
- Use
map_errto add context when propagating errors - Display user-friendly error messages in REPL
- Unit tests in the same file under
#[cfg(test)] - Use
tempfile::TempDirfor filesystem tests - Mock API calls in tests (don't require real API keys)
- Test security features thoroughly (path validation, sandboxing)
- Add doc comments (
///) for public APIs - Explain "why" not just "what" in complex sections
- Keep comments up-to-date with code changes
- README.md is the primary user documentation
- Avoid self-explanatory comments - only comment what is non-obvious or explains important "why"
- Keep README clean and simple - focus on essential user-facing information
Ï Critical Security Features:
-
Path Validation (filesystem.rs:validate_path)
- Canonicalize paths to resolve symlinks
- Check that canonical path starts with workspace
- Reject parent traversal (
..) - Reject absolute paths
- This is the first line of defense - NEVER bypass!
-
Bash Command 3-Tier Permission System (permissions.rs + bashexec.rs)
- Tier 1 (Allowed): Predefined safe commands (build tools, read-only operations)
- Automatically executed without user confirmation
- Examples: cargo, npm, ls, cat, grep, git status
- Tier 2 (Forbidden): Predefined dangerous commands
- Always blocked with clear error messages
- Examples: rm, sudo, chmod, mkdir, cd, git push
- Tier 3 (Ask): Unknown commands
- Prompt user for permission
- Can be temporarily allowed or permanently remembered
- Decisions stored in
.sofos/config.local.toml
- Additional structural checks always enforced:
- No parent traversal (
..) - No absolute paths
- No output redirection
- Git operations limited to read-only
- No parent traversal (
- Tier 1 (Allowed): Predefined safe commands (build tools, read-only operations)
-
File Size Limits
- Read operations: 10MB limit
- Bash output: 50MB limit
- Prevents memory exhaustion attacks
-
User Confirmations
- Delete operations require interactive confirmation
- Unknown bash commands prompt for permission
- Shows what will be executed/deleted before action
- User can cancel by declining
When Adding New Features:
- Always consider security implications first
- Add path validation for any new file operations
- Update permission lists if adding bash command capabilities
- Test with malicious inputs (path traversal attempts, etc)
Session File Location:
- Stored in
.sofos/sessions/{session_id}.json - Index file:
.sofos/sessions/index.json - Entire
.sofos/directory is gitignored
User sends message:
- REPL adds to conversation history as user message
- Creates API request with all messages + system prompt
- Sends to Claude API
Claude responds with tools (iterative loop):
- Response contains text + tool_use blocks
- REPL adds full response (with both text and tool_use) to history as assistant message
- Executes each tool sequentially
- Collects all tool results
- Adds all results as single user message
- Makes new API request and continues loop with new response
- Loop exits when Claude's response contains no tool calls
Important: Tool results must be in a user message, with tool_use_id matching the original tool_use id.
Problem: Claude can make infinite tool calls if it gets stuck in a loop
Solution: (src/repl/response_handler.rs)
- Use an iterative loop instead of recursion for constant stack space
- Track iteration count in the loop
- MAX_TOOL_ITERATIONS = 200
- If exceeded, inject a system interruption message into the conversation
- Claude receives the interruption and can provide a summary and suggestions
- Clear, predictable control flow without recursion overhead
- Graceful degradation: Claude knows it was interrupted and can help the user recover
When waiting for Claude's response after tool execution:
- Show animated spinner ("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
- Orange color (0xFF, 0x99, 0x33)
- "Thinking..." text
- Clears when response arrives
tokio: Async runtime for HTTP requestsreqwest: HTTP client for API callsserde,serde_json: Serialization/deserializationcolored: Terminal colors and formattingrustyline: REPL with readline supportclap: Command-line argument parsingsimilar: Text diffing for visual change display
ripgrep: Code search functionality (runtime check)- Morph API: Ultra-fast code editing (via MORPH_API_KEY)
tokio+reqwest: Industry standard for async HTTP in Rustrustyline: Best readline implementation for Rust CLIscolored: Simple, cross-platform terminal colors- Native dependencies minimal (only ripgrep, which is optional)
- Path validation with various malicious inputs
- Bash command blocklist effectiveness
- Session save/load with different formats
- Message trimming behavior
- Tool execution and result collection
- Actual Claude API responses (too expensive, non-deterministic)
- Actual file I/O in most cases (use TempDir)
- Network requests (mock when possible)
cargo test # All tests
cargo test filesystem # Just filesystem tests
cargo test -- --nocapture # Show println output- Add variant to
src/tools/tool_name.rs(ToolName enum, as_str, from_str) - Define tool schema in
src/tools/types.rsand add to tool lists - Implement execution in
src/tools/mod.rs(ToolExecutor::execute match arm) - Add tests in
src/tools/tests.rs - Update README.md with tool description
To add a safe command to Tier 1 (Allowed):
- Add to
allowed_commandsHashSet inpermissions.rs:new() - Test that command executes without prompting
- Document in README.md if it's a major addition
To add a dangerous command to Tier 2 (Forbidden):
- Add to
forbidden_commandsHashSet inpermissions.rs:new() - Add helpful error message if needed
- Test that command is blocked
- Document restriction in README.md
User-specific permissions (Tier 3):
- Stored in
.sofos/config.local.toml(gitignored) - Format:
allow = ["Bash(command:*)"]for wildcards,allow = ["Bash(exact command)"]for exact matches - Can be edited manually or via interactive prompts
- Update types in
src/api/types.rs - Add serde attributes for proper serialization
- Handle in response processing (repl.rs:handle_response)
- Test with actual API if possible
Set SOFOS_DEBUG=1 environment variable:
SOFOS_DEBUG=1 cargo runThis prints:
- Recursion depth at each step
- Number of tools being executed
- Tool success/failure and output length
- Conversation state before API calls
- Uses Claude Messages API (not legacy Completions)
- Model: claude-sonnet-4-6 (default)
- Supports tool calling and server-side tools (web_search)
- API version: 2023-06-01
- Usage tracking: Automatic token counting and cost calculation
- Uses Morph Apply REST API
- Model: morph-v3-fast (default)
- Optional integration via MORPH_API_KEY
- Provides
morph_edit_filetool when available
Potential Improvements:
- Streaming responses for faster perceived performance
- Multiple parallel tool executions (currently sequential)
- Richer TUI with panels and split views
- Plugin system for custom tools
- Configuration file for default settings
Constraints:
- Keep binary size small (currently ~5MB)
- Maintain zero-setup experience (except API keys)
- Keep security as top priority
Always:
- Test security features when making changes to filesystem or bash tools
- Update both README.md and this CLAUDE.md when adding features
- Run
cargo testbefore committing - Add helpful error messages for user-facing errors
Never:
- Skip path validation in file operations
- Add commands to bash without security review
- Panic in user-facing code (use Result and show errors gracefully)
- Break the tool calling protocol (tool_use -> tool_result matching)
- Commit API keys or sensitive data
Code Review Checklist:
- Security: Path validation present and correct?
- Security: New bash commands in blocklist if needed?
- Error handling: All Results properly handled?
- Tests: Added tests for new functionality?
- Docs: Updated README if user-visible change?
- UX: Error messages clear and actionable?
This file is loaded by Sofos and appended to the system prompt, providing deep project context for AI-assisted development.