This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Always use uv for package management, not pip:
# Install dependencies
uv sync
# Add a new dependency
uv add package-name
# Run Python scripts
uv run python script.py
# Run the CLI
uv run agentao
# or
uv run python main.py
# Quick start
./run.sh
# Or directly
uv run agentao
# Or via Python
uv run python main.py
# Run all tests with pytest
uv run python -m pytest tests/
# Run a specific test file
uv run python tests/test_imports.py
uv run python tests/test_tool_confirmation.py
uv run python tests/test_readchar_confirmation.py
uv run python tests/test_date_in_prompt.py
# All test files are in tests/ directory
Copy and edit .env from .env.example:
cp .env.example .env
# Edit .env with your API key and settings
Required: OPENAI_API_KEY
Optional: OPENAI_BASE_URL, OPENAI_MODEL
Agentao uses a Tool-Agent-CLI architecture:
cli.py): User interface with Rich, handles commands, manages session state (like allow_all_tools)agent.py): Orchestrates LLM, tools, skills, and conversation historytools/): Individual tool implementations following the Tool base classUser → CLI → Agent → LLM + Tools
↓
SkillManager (loads from skills/)
All tools inherit from Tool base class (tools/base.py):
class MyTool(Tool):
@property
def name(self) -> str:
return "my_tool"
@property
def description(self) -> str:
return "Description for LLM"
@property
def parameters(self) -> Dict[str, Any]:
return {...} # JSON Schema
@property
def requires_confirmation(self) -> bool:
return False # True for dangerous operations
def execute(self, **kwargs) -> str:
return "Result"
Tool Registration: Tools are registered in agent.py::_register_tools(). The ToolRegistry converts them to OpenAI function calling format.
Tool Confirmation: Tools with requires_confirmation=True (Shell, Web, File Writing) pause execution and prompt user via confirmation_callback passed from CLI.
Tools requiring confirmation:
run_shell_command - Shell command execution (allowlist for safe read-only commands)web_fetch - Fetch web content (domain-tiered: allowlist/blocklist/ask)google_web_search - Web searchwrite_file - File writing/overwriting (prevents data loss)Domain-Based Permissions (web_fetch): The PermissionEngine supports "domain" rules with allowlist/blocklist matching. Default presets auto-allow trusted docs sites (.github.com, .docs.python.org, etc.) and auto-deny SSRF targets (localhost, 127.0.0.1, 169.254.169.254, etc.). Customizable via .agentao/permissions.json. See docs/features/TOOL_CONFIRMATION_FEATURE.md for details.
Dynamic Loading: Skills are auto-discovered from skills/ directory. Each subdirectory contains:
SKILL.md - Main file with YAML frontmatter (name:, description:)reference/*.md (optional) - Additional documentation loaded on-demandSkill Manager (skills/manager.py):
available_skills dict (all skills)active_skills dict (currently activated)Activation: Use activate_skill tool or /skills command. Active skills add their documentation to the system prompt.
The system prompt is dynamically built in agent.py::_build_system_prompt():
YYYY-MM-DD HH:MM:SS (Day)This composition happens on every chat() call to keep skills context fresh.
# agent.py::chat()
1. User message added to self.messages
2. System prompt built (includes AGENTAO.md, date, skills)
3. LLM called with messages + tools
4. Loop (max 100 iterations):
a. If tool_calls: execute each tool
- Check requires_confirmation
- Call confirmation_callback if needed
- Execute tool or cancel based on response
b. Add tool results to messages
c. Call LLM again with updated messages
d. If no tool_calls: return final response
Complete LLM interaction logging to agentao.log:
Logger is in llm/client.py. To debug tool execution or LLM behavior, check this log file.
User commands (start with /):
/clear - Clears history AND resets allow_all_tools to False/reset-confirm - Resets allow_all_tools only (keeps history)/status - Shows message count, model, active skills, confirmation mode/model [name] - List models or switch to specified model/skills - List available/active skills/memory - Show saved memories/mcp - List MCP servers and tools/help - Show helpSession state allow_all_tools persists across tool confirmations within one session.
Agentao supports connecting to external MCP servers that provide additional tools.
Configuration: .agentao/mcp.json (project) and <home>/.agentao/mcp.json (global):
{
"mcpServers": {
"server-name": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"],
"env": { "TOKEN": "$MY_TOKEN" },
"trust": false
},
"remote-server": {
"url": "https://api.example.com/sse",
"headers": { "Authorization": "Bearer $API_KEY" },
"timeout": 30
}
}
}
Transport types: command (stdio subprocess) or url (SSE).
Tool naming: MCP tools are registered as mcp_{server}_{tool} (e.g. mcp_github_create_issue).
Architecture:
.agentao/mcp.json → McpConfig → McpClientManager → McpClient (per server)
↓
list_tools() / call_tool()
↓
McpTool(Tool) → ToolRegistry
Key files:
agentao/mcp/config.py - Config loading, env var expansionagentao/mcp/client.py - McpClient (single server), McpClientManager (multi-server)agentao/mcp/tool.py - McpTool wrapper adapting MCP tools to Tool base classAsync bridge: MCP SDK is async-only; McpClientManager uses a dedicated event loop with run_until_complete() to bridge into sync Agentao code.
CLI: /mcp list, /mcp add <name> <command|url>, /mcp remove <name>
agentao/tools/<module>.pyTool interface (name, description, parameters, execute)requires_confirmation=True if dangerous:
agent.py::_register_tools():
from .tools.mymodule import MyTool
# In _register_tools():
tools_to_register.append(MyTool())
skills/my-skill/SKILL.md with YAML frontmatter:
---
name: my-skill
description: Use when... (trigger conditions)
---
# Skill Documentation
...
reference/*.md files for on-demand loadingReference files are loaded only when skill is activated (saves memory).
CLI creates callback and passes to Agent:
# cli.py
def confirm_tool_execution(self, name, desc, args) -> bool:
# Show menu, get user choice
# Return True/False
self.agent = Agentao(
confirmation_callback=self.confirm_tool_execution
)
Agent checks before tool execution:
# agent.py
if tool.requires_confirmation and self.confirmation_callback:
confirmed = self.confirmation_callback(name, desc, args)
if not confirmed:
result = "Tool execution cancelled by user"
Uses readchar library for instant response:
import readchar
key = readchar.readkey() # No Enter needed
Supports: 1, 2, 3, Esc, Ctrl+C. Invalid keys are silently ignored.
Architecture: SQLite-backed storage managed by MemoryManager (agentao/memory/manager.py).
SQLite databases:
| Database | Path | Content |
|---|---|---|
| Project store | .agentao/memory.db |
Project-scoped persistent memories + session summaries |
| User store | <home>/.agentao/memory.db |
Cross-project user-scoped persistent memories |
Three data types:
Persistent memories (MemoryRecord) — rows in the memories table. Soft-deleted (never physically removed). Scoped to user or project. Types: preference, profile, project_fact, workflow, decision, constraint, note. Source: explicit (LLM-written) or auto/crystallized. Fields: id, scope, type, key_normalized, title, content, tags, keywords, source, confidence, sensitivity, created_at, updated_at, deleted_at.
Session summaries (SessionSummaryRecord) — rows in the session_summaries table. Written by the context-compression pipeline (microcompaction / full LLM summarization) to preserve conversation continuity across compaction events. Scoped to a session_id.
Recall candidates (RecallCandidate) — transient, in-memory only. Scored at query time by MemoryRetriever using a keyword/Jaccard/tag/recency formula. Never stored.
Prompt injection (per turn, two blocks):
<memory-stable> — rendered by MemoryPromptRenderer.render_stable_block(): stable persistent memories only (budget-limited, selection policy applied). Session summaries are intentionally excluded — they already live in the conversation message history as [Conversation Summary] blocks.<memory-context> — rendered by render_dynamic_block(): top-k recall candidates scored against the current user message.LLM tool (write-only):
save_memory(key, value, tags?) — the only memory tool exposed to the LLMCLI commands (full management):
/memory / /memory list — list all entries/memory search <query> — keyword search across title, value, tags/memory tag <tag> — filter by tag/memory user / /memory project — show a single scope/memory delete <key> — soft-delete by title/memory clear — soft-delete all entries + clear session summaries (with confirmation)/memory session — show current session summary/memory status — entry counts, session size, archive countSeparation of concerns: The LLM can only write (save_memory). Search, delete, and clear are CLI-only operations that call MemoryManager methods directly — they are never exposed to the LLM as callable tools.
See docs/features/memory-management.md for detailed documentation.
agentao/
├── agentao/ # Main package
│ ├── agent.py # Core orchestration
│ ├── cli.py # CLI interface with Rich
│ ├── llm/
│ │ └── client.py # OpenAI client wrapper
│ ├── tools/ # Tool implementations
│ │ ├── base.py # Tool base class + registry
│ │ ├── file_ops.py # Read, write, edit, list
│ │ ├── search.py # Glob, grep
│ │ ├── shell.py # Shell execution
│ │ ├── web.py # Fetch, search
│ │ ├── memory.py # Persistent memory
│ │ ├── agents.py # Helper agents
│ │ └── skill.py # Skill activation
│ ├── mcp/ # MCP (Model Context Protocol) support
│ │ ├── config.py # Config loading + env var expansion
│ │ ├── client.py # McpClient + McpClientManager
│ │ └── tool.py # McpTool wrapper for Tool interface
│ └── skills/
│ └── manager.py # Skill loading + management
├── skills/ # Skill definitions (SKILL.md files)
├── tests/ # Test files (test_*.py)
├── docs/ # Documentation
│ ├── features/ # Feature documentation
│ ├── updates/ # Update logs
│ ├── implementation/ # Technical implementation details
│ └── dev-notes/ # Development notes (archived)
├── CLAUDE.md # Claude Code guidance
├── AGENTAO.md # Project-specific instructions
└── main.py # Entry point
openai - LLM client (OpenAI-compatible APIs)rich - CLI interface (markdown, panels, prompts)readchar - Single-key input (no Enter needed)httpx - HTTP client for web toolsbeautifulsoup4 - HTML parsingpython-dotenv - Environment configurationmcp - Model Context Protocol client SDK