AGENTS.md

Rumilo Agent Guide

CLI that dispatches specialized AI research subagents for web research and git repository exploration.

Commands

bun run dev       # Run CLI via bun (development)
bun run build     # Build to dist/
bun run typecheck # TypeScript check (also: bun run lint)

Run bun test to execute the test suite.

Architecture

CLI entry (src/cli/index.ts)
    ├── web command → runWebCommand() → runAgent() with web tools
    └── repo command → runRepoCommand() → runAgent() with git tools

Control Flow

  1. CLI parses args with custom parser (no library), dispatches to command handlers
  2. Command handlers (src/cli/commands/) load config, create workspace, build context-aware system prompt, configure tools, invoke runAgent()
  3. Agent runner (src/agent/runner.ts) wraps @mariozechner/pi-agent - creates Agent, subscribes to events, prompts, extracts final message
  4. Tools are created via factory functions that close over workspace path for sandboxing
  5. Workspace (src/workspace/manager.ts) is a temp directory (cleaned up unless --no-cleanup)

Two Agent Modes

Mode Tools External Services
web web_search, web_fetch, read, grep, ls, find Kagi (search), Tabstack (fetch→markdown)
repo read, grep, ls, find, git_log, git_show, git_blame, git_diff, git_refs, git_checkout None (clones repo to workspace)

Key Patterns

Tool Factory Pattern

All tools follow the same signature:

const createFooTool = (workspacePath: string): AgentTool => ({ ... })

Tools use @sinclair/typebox for parameter schemas. Execute functions return { content: [{type: "text", text}], details?: {...} }.

Workspace Sandboxing

Filesystem tools (read, grep, ls, find) must constrain paths to workspace:

  • ensureWorkspacePath() in src/agent/tools/index.ts validates paths don't escape
  • resolveToCwd() / resolveReadPath() in src/agent/tools/path-utils.ts handle expansion and normalization

Git tools (git_show, git_blame, git_diff, git_checkout, git_log, git_refs) do not apply path containment. Refs and paths are passed directly to simple-git, which is initialized with workspacePath so all commands are scoped to the cloned repository. The user explicitly chooses which repository to clone, so its git objects are trusted content. This is an accepted trust boundary: we sandbox the filesystem but trust git data within the user's chosen repo.

Config Cascade

defaultConfig → XDG_CONFIG_HOME/rumilo/config.toml → CLI flags

Config uses TOML, validated against TypeBox schema (src/config/schema.ts).

Model Resolution

Model strings use provider:model format. custom:name prefix looks up custom model definitions from config's [custom_models] section. Built-in providers delegate to @mariozechner/pi-ai.

API key resolution for custom models uses resolveConfigValue() from src/util/env.ts, which supports bare env var names, $VAR / ${VAR} references, and !shell-command execution. Built-in providers fall back to pi-ai's getEnvApiKey() (e.g. ANTHROPIC_API_KEY).

Error Handling

Custom error classes in src/util/errors.ts extend RumiloError with error codes:

  • ConfigError, FetchError, CloneError, WorkspaceError, ToolInputError

System Prompts

Prompts in src/agent/prompts/ are builder functions, not static strings. Each takes a context object and returns a templated prompt with XML-style sections (<approach>, <answering>, <environment>).

  • Repo prompt (buildRepoPrompt): receives { currentTime, hasHistory }. Git history guidance is only included when hasHistory is true (i.e. --full clone). Instructs the agent to check for agent instruction files (AGENTS.md, CLAUDE.md, .cursorrules, etc.) before README when orienting.
  • Web prompt (buildWebPrompt): receives { currentTime }. Provider-agnostic — does not assume a specific search-then-fetch workflow.

Custom system_prompt_path in config replaces the built prompt entirely.

Output Truncation

src/util/truncate.ts handles large content:

  • DEFAULT_MAX_LINES = 2000
  • DEFAULT_MAX_BYTES = 50KB
  • GREP_MAX_LINE_LENGTH = 500 chars

Gotchas

  1. Grep requires ripgrep - createGrepTool checks for rg at tool creation time and throws if missing
  2. Web tools require credentials - KAGI_SESSION_TOKEN and TABSTACK_API_KEY via env or config
  3. Shallow clones by default - repo mode uses --depth 1 --filter=blob:limit=5m unless --full
  4. Pre-fetch injection - web command with -u URL wraps content in <attached_content> XML tags; content ≤50KB is inlined, larger content is stored in workspace
  5. No .js extension in imports - source uses .js extensions for ESM compatibility even though files are .ts

Adding a New Tool

  1. Create src/agent/tools/newtool.ts following the factory pattern
  2. Export from src/agent/tools/index.ts if general-purpose
  3. Add to appropriate command's tool array in src/cli/commands/{web,repo}.ts
  4. Use ToolInputError for validation failures