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
- CLI parses args with custom parser (no library), dispatches to command handlers
- Command handlers (
src/cli/commands/) load config, create workspace, build context-aware system prompt, configure tools, invokerunAgent() - Agent runner (
src/agent/runner.ts) wraps@mariozechner/pi-agent- createsAgent, subscribes to events, prompts, extracts final message - Tools are created via factory functions that close over workspace path for sandboxing
- 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()insrc/agent/tools/index.tsvalidates paths don't escaperesolveToCwd()/resolveReadPath()insrc/agent/tools/path-utils.tshandle 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 whenhasHistoryis true (i.e.--fullclone). 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 = 2000DEFAULT_MAX_BYTES = 50KBGREP_MAX_LINE_LENGTH = 500chars
Gotchas
- Grep requires ripgrep -
createGrepToolchecks forrgat tool creation time and throws if missing - Web tools require credentials -
KAGI_SESSION_TOKENandTABSTACK_API_KEYvia env or config - Shallow clones by default - repo mode uses
--depth 1 --filter=blob:limit=5munless--full - Pre-fetch injection - web command with
-u URLwraps content in<attached_content>XML tags; content ≤50KB is inlined, larger content is stored in workspace - No
.jsextension in imports - source uses.jsextensions for ESM compatibility even though files are.ts
Adding a New Tool
- Create
src/agent/tools/newtool.tsfollowing the factory pattern - Export from
src/agent/tools/index.tsif general-purpose - Add to appropriate command's tool array in
src/cli/commands/{web,repo}.ts - Use
ToolInputErrorfor validation failures