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)

No test suite currently exists (test/ is empty).

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, 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

Tools 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

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.

Error Handling

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

  • ConfigError, FetchError, CloneError, WorkspaceError, ToolInputError

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 threshold - web command with -u URL injects content directly if ≤50KB, otherwise stores 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