Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ release-notes.md
.vscode/
.opencode/
.entire/
.kiro/
.agents/
186 changes: 186 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,192 @@ A lightweight CLI for running single-purpose LLM agents. Think `make` for AI —
- Flags override TOML overrides defaults. Resolution order matters everywhere.
- When in doubt, print nothing. Axe output MUST be safe to pipe.

## Directory Structure

```text
axe/
├── cmd/ # CLI commands (run, agents, config, gc, version)
├── internal/
│ ├── agent/ # Agent TOML loading and validation
│ ├── config/ # Global config.toml handling
│ ├── envinterp/ # ${VAR} expansion for MCP headers
│ ├── mcpclient/ # Model Context Protocol client
│ ├── memory/ # Persistent memory system
│ ├── provider/ # LLM provider implementations (Anthropic, OpenAI, Ollama, OpenCode, Bedrock)
│ ├── refusal/ # LLM refusal detection
│ ├── resolve/ # Context resolution (workdir, skill, files, stdin)
│ ├── testutil/ # Test helpers and mock servers
│ ├── tool/ # Built-in tools (file ops, shell, web)
│ ├── toolname/ # Tool name constants
│ └── xdg/ # XDG Base Directory support
├── docs/
│ ├── design/ # Architecture docs
│ └── plans/ # Implementation specs
├── examples/ # Example agents (code-reviewer, commit-msg, summarizer)
└── main.go
```

## Key Entry Points

### CLI Commands (`cmd/`)

- **run.go** - Main execution: loads agent, resolves context, calls provider, handles conversation loop (max 50 turns), executes tools (parallel by default), manages memory
- **agents.go** - Agent management: list, show, init (scaffold), edit (opens $EDITOR)
- **config.go** - Config init (creates XDG dirs, embeds sample skill), path display
- **gc.go** - Memory garbage collection with LLM-assisted pattern detection or fallback to max_entries
- **root.go** - Root command, exit code mapping (0=success, 1=runtime error, 2=config error)

### Core Packages

- **internal/agent** - `Load()` parses TOML, `Validate()` checks required fields and constraints, `Scaffold()` generates template
- **internal/provider** - `Provider` interface with `Send(ctx, Request) (Response, error)`. Implementations: Anthropic (messages API), OpenAI (chat completions), Ollama (local), OpenCode (multi-route: Claude/messages, GPT/responses, others/chat/completions), Bedrock (AWS Converse API)
- **internal/tool** - Registry pattern. Built-in tools: list_directory, read_file (with pagination), write_file, edit_file (find/replace), run_command (via sh -c), url_fetch (HTML stripping), web_search, call_agent (sub-agent delegation). All file tools sandboxed to workdir.
- **internal/resolve** - Resolution order: flags > TOML > env vars > defaults. Handles tilde/env var expansion, glob patterns (simple and **), binary file detection, symlink escape prevention.
- **internal/memory** - Timestamped markdown entries. `AppendEntry()` adds task/result, `LoadEntries()` loads last N, `TrimEntries()` keeps last N, `CountEntries()` returns total.
- **internal/mcpclient** - MCP client for stdio/HTTP transports. `Connect()`, `ListTools()`, `CallTool()`. Router manages multiple servers, namespaces tools by server name.

## Configuration

### Agent TOML (`$XDG_CONFIG_HOME/axe/agents/*.toml`)

```toml
name = "agent-name" # Required
model = "provider/model-name" # Required (e.g., anthropic/claude-sonnet-4-20250514)
description = "..." # Optional
system_prompt = "..." # Optional
skill = "path/to/SKILL.md" # Optional (absolute, relative to config dir, or bare name)
files = ["**/*.go", "README.md"] # Optional (glob patterns)
workdir = "/path/to/dir" # Optional (default: cwd, supports ~ and $VAR)
tools = ["read_file", "write_file"] # Optional (valid: list_directory, read_file, write_file, edit_file, run_command, url_fetch, web_search)
sub_agents = ["agent1", "agent2"] # Optional (allowed sub-agent names)

[sub_agents_config]
max_depth = 3 # Default: 3, Max: 5
parallel = true # Default: true (run sub-agents concurrently)
timeout = 120 # Default: 120 seconds

[memory]
enabled = true # Default: false
last_n = 10 # Default: 10 (load last N entries into context)
max_entries = 100 # Default: 100 (warn when exceeded)
path = "/custom/path" # Optional (default: $XDG_DATA_HOME/axe/memory/{agent-name}.md)

[params]
temperature = 0.7 # Default: 0 (omitted if 0)
max_tokens = 4096 # Default: 0 (provider default)

[[mcp_servers]]
name = "server-name"
transport = "stdio" # or "http", "https"
command = "/path/to/server"
args = ["--flag"]
env = {"KEY" = "value"}

[[mcp_servers]]
name = "http-server"
transport = "https"
url = "https://example.com/mcp"
headers = {"Authorization" = "Bearer ${API_KEY}"} # ${VAR} expanded from env
```

### Global Config (`$XDG_CONFIG_HOME/axe/config.toml`)

```toml
[providers.anthropic]
api_key = "sk-ant-..."
base_url = "https://api.anthropic.com"

[providers.openai]
api_key = "sk-..."
base_url = "https://api.openai.com"

[providers.ollama]
base_url = "http://localhost:11434"

[providers.bedrock]
region = "us-east-1"
```

**Env vars override config file:**
- `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`
- `AXE_ANTHROPIC_BASE_URL`, `AXE_OPENAI_BASE_URL`, `AXE_OLLAMA_BASE_URL`, `AXE_OPENCODE_BASE_URL`
- `AWS_REGION`, `AWS_DEFAULT_REGION` (for Bedrock)
- `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` (for Bedrock)
- `TAVILY_API_KEY` (overrides config for web search API key), `AXE_WEB_SEARCH_BASE_URL`

## Repo-Specific Patterns

### Testing

- **Table-driven tests** - See any `*_test.go` for examples. Use `[]struct{name, input, want}` pattern.
- **Mock servers** - `internal/testutil/mockserver.go` provides `NewMockLLMServer()` for Anthropic/OpenAI responses. Use in integration tests.
- **Golden files** - `cmd/golden_test.go` and `cmd/testdata/golden/` for CLI output testing. Update with `UPDATE_GOLDEN=1 go test`.
- **Fixtures** - `cmd/testdata/agents/` and `cmd/testdata/skills/` for test data. Use `testutil.SeedFixtureAgents()` and `testutil.SeedFixtureSkills()`.
- **Integration tests** - `cmd/run_integration_test.go` and `cmd/run_mcp_integration_test.go` compile binary and run end-to-end tests.
- **Smoke tests** - `cmd/smoke_test.go` runs actual binary with real config (requires API keys in CI).

### Error Handling

- **Provider errors** - Wrapped in `ProviderError` with category (Auth, RateLimit, Server, Input, Network, Unknown). See `internal/provider/provider.go`.
- **Tool errors** - Returned as tool results to LLM, not fatal. LLM can retry or adjust.
- **Config errors** - Exit code 2. Validation happens in `internal/agent/agent.go` `Validate()`.
- **Runtime errors** - Exit code 1. Mapped in `cmd/run.go` `mapProviderError()`.

### Path Security

- **All file tools sandboxed** - `internal/tool/path_validation.go` `validatePath()` blocks absolute paths, `..` traversal, symlink escapes.
- **Working directory enforcement** - All file operations relative to agent's workdir.
- **Skill script paths must be absolute** - Scripts in SKILL.md run from agent's workdir, not skill dir. Use absolute paths like `/home/user/.config/axe/skills/my-skill/scripts/fetch.sh`.

### Conversation Loop

- **Max 50 turns** - Hard limit in `cmd/run.go` `runAgent()`. Prevents infinite loops.
- **Stops on text response** - Loop exits when LLM returns text without tool calls.
- **Parallel tool execution** - Default behavior. Set `sub_agents_config.parallel = false` for sequential.
- **Tool call details** - Captured in JSON output with `--json` flag. Includes arguments, results, truncation status, errors.

### Sub-Agent Delegation

- **Opaque execution** - Parent never sees sub-agent's internal turns, tool calls, or files. Only final text result.
- **Depth tracking** - Incremented on each delegation. `call_agent` tool removed at max depth.
- **Allowed list** - Sub-agent name must be in parent's `sub_agents` list.
- **Independent context** - Sub-agent loads its own TOML, skill, files. No context inheritance except the task string.

### Memory System

- **Markdown format** - `## YYYY-MM-DDTHH:MM:SS±HH:MM` headers, `**Task:**` and `**Result:**` sections.
- **Result truncation** - Results truncated to 1000 chars when appending.
- **Load on run** - Last N entries loaded into system prompt if memory enabled.
- **Append on success** - Entry added only if agent run succeeds (no API errors).
- **GC with LLM** - `axe gc agent` uses LLM to detect patterns and suggest keep count. Falls back to max_entries if LLM fails.

### MCP Integration

- **Tool namespacing** - MCP tools prefixed with server name: `server-name.tool-name`.
- **Built-in collision** - MCP tools with same name as built-ins are skipped (built-ins win).
- **Transport validation** - Only stdio, http, https allowed. Validated in `internal/agent/agent.go`.
- **Header expansion** - HTTP headers support `${VAR}` syntax, expanded from env vars.
- **Type coercion** - MCP tool arguments coerced to match schema (string/number/boolean).

## CI/CD

- **GitHub Actions** - `.github/workflows/go.yml` runs tests and linting on push/PR.
- **Release workflow** - `.github/workflows/release.yml` uses GoReleaser on tag push.
- **GoReleaser** - `.goreleaser.yml` builds multi-platform binaries, creates GitHub release.
- **Linting** - `.golangci.yml` configures golangci-lint. Run with `golangci-lint run`.

## Docker

- **Dockerfile** - Multi-stage build: golang:1.25-alpine → alpine:latest.
- **Security** - Non-root user (UID 10001), read-only rootfs, all caps dropped, no new privileges.
- **Volumes** - Mount config to `/home/axe/.config/axe`, data to `/home/axe/.local/share/axe`.
- **Compose** - `docker-compose.yml` includes Ollama sidecar with `--profile ollama`.

## Project Docs

Design docs live in `./docs/` — check milestones, config schema, sub-agent patterns, and CLI structure there before making architectural changes.

## Custom Instructions
<!-- This section is for human and agent-maintained operational knowledge.
Add repo-specific conventions, gotchas, and workflow rules here.
This section is preserved exactly as-is when re-running codebase-summary. -->
Loading
Loading