Imported from debamitro/one-shot-cli-agent (
AGENTS.md). Install upstream withnpx skills add debamitro/one-shot-cli-agent. Copyright stays with the author.
AGENTS.md
This file provides guidance to agents (i.e., ADAL) when working with code in this repository.
Project Overview
CodeAgent is a Rust-based CLI for building interactive coding agents with LLM support (OpenAI and Anthropic). It features a REPL interface, tool execution system, and persistent session management.
Key Tech Stack:
- Language: Rust 1.70+
- LLM SDKs:
async-openaifor OpenAI, custom Anthropic client viareqwest - Tools: File ops, code search (ripgrep), command execution
- Storage: JSON sessions in
~/.codeagent/sessions/
Essential Commands
Development & Testing
# Build (development mode)
cargo build
# Run in development (with provider and API key)
cargo run -- --provider openai --api-key sk-...
# Run tests
cargo test
# Build release binary
cargo build --release
# Binary location: target/release/codeagent
Running the Agent
# With API key as argument
codeagent --provider openai --api-key sk-... --directory /path/to/project
# Using environment variables (recommended)
export OPENAI_API_KEY=sk-...
codeagent --provider openai
export ANTHROPIC_API_KEY=sk-ant-...
codeagent --provider anthropic
# Resume existing session
codeagent --provider openai --session <session-id>
Critical Dependencies
- Grep tools (for
FileSearchToolcode search):- ripgrep (recommended): Fastest, supports file type filters, installed via
cargo install ripgrep - grep (Unix fallback): Available on most Unix systems by default
- findstr (Windows fallback): Available on Windows by default
- Tool selection: Automatically tries ripgrep → grep → findstr (first available wins)
- ripgrep (recommended): Fastest, supports file type filters, installed via
- Web search: Optional, supports two providers:
- Serper API: Set
SERPER_API_KEYenv var forWebSearchTool(premium, requires API key from serper.dev) - DuckDuckGo:
WebSearchDDGToolworks out of the box (free, no API key needed)
- Serper API: Set
Architecture
System Flow
User Input (REPL)
→ Session Management (stores history)
→ Provider (OpenAI/Anthropic streaming)
→ Tool Calls (detected from LLM response)
→ Tool Execution (FileSearch, EditFile, Bash)
→ Results fed back to LLM
→ Loop until completion
→ Session saved to ~/.codeagent/sessions/<id>.json
Provider System (src/provider/)
Trait-based abstraction for LLM providers:
LLMProvidertrait defines:chat_completion(),stream_completion()- Implementations:
OpenAIProvider(usesasync-openaiSDK),AnthropicProvider(custom HTTP client) - Both return
StreamChunkwith content + tool calls - Tool definitions passed as JSON to LLM in Anthropic/OpenAI format
Key types:
Message: Standard chat message (role + content)ToolCall: LLM-requested tool execution (id + name + arguments)StreamChunk: Streaming response unit (content, tool_calls, finished flag)
Tool System (src/tools/)
Registry pattern for tool management:
- Each tool implements
Tooltrait:name(),description(),input_schema(),execute() ToolRegistrymaintainsHashMap<String, Box<dyn Tool>>- Tools registered in
src/tools/mod.rs:ToolRegistry::new(): Registers basic tools only (FileSearch, EditFile, Bash)ToolRegistry::new_with_api_keys(): Adds web search tools (conditionally based on API keys)
- LLM receives tool definitions as JSON schemas
- Tool calls executed synchronously, results added to conversation
Built-in tools:
FileSearchTool: glob (find files) + grep (search content via ripgrep)EditFileTool: create_file, replace_by_string, replace_by_lines, read_fileBashTool: Execute shell commands in working directoryWebSearchTool: Web search via Serper API (requires SERPER_API_KEY)WebSearchDDGTool: Web search via DuckDuckGo (free, no API key required)URLFetchTool: Fetch and extract content from URLs (HTML to text/markdown)
Tool output format:
pub struct ToolOutput {
pub output: serde_json::Value, // Structured data for LLM
pub observation: String, // Human-readable summary
pub display: Option<String>, // Optional detailed display
pub status: String, // "success" | "error"
}
Session Management (src/session/mod.rs)
Persistent conversation storage:
- Sessions stored as JSON in
~/.codeagent/sessions/<uuid>.json - Each session contains: metadata, full message history, tool calls, tool results
- Auto-saves after each user interaction and on exit
- Resume via
--session <id>flag
Data structures:
SessionInfo: Metadata (id, title, directory, timestamps, message count)MessagePart: Single conversation turn with role, content, tool_calls, tool_resultsToolResult: Tool execution outcome (tool_call_id, output, observation, status)
Conversation reconstruction:
get_conversation_history()flattens MessageParts into simpleMessagelist- Tool results injected as user messages: "Tool result: {observation}"
- This simplified view fed to LLM for context
Entry Point (src/main.rs)
REPL loop logic:
- Parse CLI args (provider, API key, working dir, session)
- Initialize provider + tool registry
- Load/create session
- Loop:
- Get user input
- Add to session history
- Stream LLM response (with tool definitions)
- Collect tool calls from response
- Execute tools via registry
- Add tool results to session + conversation
- If tool calls present, loop again with results as context
- If no tool calls, wait for next user input
- Auto-save session on exit
Special commands:
save: Manually save sessionexit: Save and quit
Key Implementation Details
Tool Call Loop (Agent Pattern)
The agent continues calling tools until the LLM returns a response without tool calls:
loop {
// Stream LLM response
let response = provider.stream_completion(messages, tools).await?;
// Add assistant message to history
session.add_assistant_message(content, tool_calls);
// If no tool calls, we're done
if tool_calls.is_empty() { break; }
// Execute tools and add results to messages
for tool_call in tool_calls {
let result = tool_registry.execute(&tool_call.name, tool_call.arguments)?;
session.add_tool_result(result);
messages.push(Message { role: "user", content: result.observation });
}
}
Streaming Response Handling
Both providers use tokio::sync::mpsc::Receiver<StreamChunk>:
- Main thread spawns async task for API calls
- Chunks sent via channel as they arrive
- REPL prints content immediately for real-time display
- Tool calls accumulated until stream finishes
System Prompt
Hardcoded in main.rs:
"You are a helpful coding assistant. You have access to tools for file operations,
code search, and command execution. Use them to help the user with their coding tasks."
Prepended to conversation history before every LLM call.
Gotchas & Constraints
- Ripgrep dependency: FileSearchTool will panic if
rgnot in PATH - No test coverage:
cargo testexists but no actual test files present - Single working directory: Set at startup via
--directory, not changeable mid-session - Tool execution is synchronous: No parallel tool calls (executes sequentially)
- Session format is append-only: No editing past messages, full history retained
- API keys: Must be provided via CLI arg or env var, not stored in sessions
- Provider models: Defaults used unless
--modelspecified:- OpenAI: Model set in provider initialization (check
src/provider/openai.rs) - Anthropic: Model set in provider initialization (check
src/provider/anthropic.rs)
- OpenAI: Model set in provider initialization (check
File Organization
src/
├── main.rs # CLI + REPL loop
├── provider/
│ ├── mod.rs # LLMProvider trait + types
│ ├── openai.rs # OpenAI implementation
│ └── anthropic.rs # Anthropic implementation
├── session/
│ └── mod.rs # Session storage & history
└── tools/
├── mod.rs # Tool trait + registry
├── file_search.rs # Glob + grep tools
├── edit_file.rs # File editing tools
├── bash.rs # Command execution
├── web_search.rs # Serper API web search
├── web_search_ddg.rs # DuckDuckGo web search (free)
└── url_fetch.rs # URL content fetching
Development Workflow
-
Adding a new tool:
- Implement
Tooltrait insrc/tools/your_tool.rs - Add module declaration in
src/tools/mod.rs - Register in
ToolRegistry::new()insrc/tools/mod.rs - Tool automatically available to LLM (schema extracted from trait methods)
- Implement
-
Adding a new provider:
- Implement
LLMProvidertrait insrc/provider/your_provider.rs - Add module declaration in
src/provider/mod.rs - Add match arm in
main.rsprovider initialization - Handle API key env var in args parsing
- Implement
-
Modifying session format:
- Update types in
src/session/mod.rs - Update serialization/deserialization
- No migration needed - old sessions incompatible, user creates new session
- Update types in