# Eunice

> An agentic CLI runner in Rust with unified support for OpenAI, Gemini, Claude, and Ollama. Features multi-agent orchestration, autonomous DMN mode, and MCP tool integration.

This file contains the complete documentation for Eunice in a single file for LLM consumption.

---

## README

# eunice

[![Crates.io](https://img.shields.io/crates/v/eunice.svg)](https://crates.io/crates/eunice)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

An agentic CLI runner in Rust with unified support for OpenAI, Gemini, Claude, and Ollama via OpenAI-compatible APIs.

**3,568 lines of Rust** • **4.2MB binary** - Emphasizing "sophisticated simplicity".

**Homepage**: [longrunningagents.com](https://longrunningagents.com)

## Features

- **Multi-Provider Support**: OpenAI, Google Gemini, Anthropic Claude, and local Ollama models
- **Unified API**: Uses OpenAI-compatible endpoints for all providers
- **MCP Integration**: Model Context Protocol servers for extensible tool capabilities
- **Multi-Agent Orchestration**: Agents can invoke other agents as tools for complex workflows
- **Smart Defaults**: Automatically selects the best available model
- **DMN Mode**: Default Mode Network - autonomous batch execution with pre-configured MCP tools for software engineering
- **Webapp Mode**: Browser-based interface via `--webapp` with real-time streaming and server-side sessions
- **Intelligent Rate Limiting**: Automatic 429 retry with 6-second backoff in DMN mode
- **Interactive Mode**: Multi-turn conversations with context preservation
- **Progress Spinners**: Visual feedback during tool execution

## Installation

### From crates.io

```bash
cargo install eunice
```

### From Source

```bash
cargo build --release
```

The binary will be at `target/release/eunice`.

### Install Globally (from source)

```bash
cargo install --path .
```

## Quick Start

![Basic Usage](assets/demo_basic.gif)

```bash
# Zero-config: auto-discovers eunice.json + prompt.txt in current directory
eunice

# Use smart default model (prefers Gemini)
eunice "What files are in this directory?"

# Specify a model
eunice --model gemini-2.5-flash "Explain this code"
eunice --model sonnet "Review this implementation"
eunice --model llama3.1:latest "Summarize this text"

# Interactive mode
eunice --interact

# With MCP tools
eunice --config ./mcp-config.json "What time is it?"

# DMN mode - autonomous batch execution (auto-loads 7 MCP servers)
eunice --dmn "Fix the bug in main.rs"
```

## Command Line Options

```
Usage: eunice [OPTIONS] [PROMPT]

Arguments:
  [PROMPT]  Prompt as positional argument (can be file path or string)

Options:
      --model <MODEL>           AI model to use
      --prompt <PROMPT>         Prompt as file path or string
      --tool-output-limit <N>   Limit tool output display (default: 50, 0=unlimited)
      --list-models             Show all available models
      --config <FILE>           Path to MCP configuration JSON/TOML
      --no-mcp                  Disable MCP even if eunice.json exists
      --default-mode-network    Enable DMN mode with auto-loaded MCP tools [aliases: --dmn]
      --webapp                  Start web server interface (default: 0.0.0.0:8811)
      --agent <NAME>            Run as specific agent (default: root if agents configured)
      --list-agents             List configured agents
  -i, --interact                Interactive mode for multi-turn conversations
      --silent                  Suppress all output except AI responses
      --verbose                 Enable verbose debug output
      --events                  Output JSON-RPC events to stdout
      --create-agent            Interactive wizard to create eunice.toml
  -h, --help                    Print help
  -V, --version                 Print version
```

## Provider Support

### OpenAI
- Models: `gpt-5.1`, `gpt-5.1-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex-max`
- Legacy: `gpt-4o`, `gpt-4-turbo`, `o1`, `o3`, `o3-mini`
- Requires: `OPENAI_API_KEY`

### Google Gemini
- Models: `gemini-3-pro-preview` (default), `gemini-2.5-flash`, `gemini-2.5-pro`, `gemini-1.5-flash`, `gemini-1.5-pro`
- Requires: `GEMINI_API_KEY`

### Anthropic Claude
- Models: `opus`, `sonnet`, `haiku` (aliases for latest versions)
- Version aliases: `opus-4.5`, `opus-4.1`, `sonnet-4.5`, `haiku-4.5`
- Full names: `claude-opus-4-5-20251101`, `claude-sonnet-4-20250514`, etc.
- Requires: `ANTHROPIC_API_KEY`

### Ollama (Local)
- Models: Any installed model (`llama3.1:latest`, `deepseek-r1:latest`, etc.)
- Requires: Ollama running at `http://localhost:11434` (or `OLLAMA_HOST`)

## Smart Model Selection

When no model is specified, eunice automatically selects the best available:

1. **Gemini** (if API key set): `gemini-3-pro-preview`
2. **Anthropic** (if API key set): `sonnet` (Claude Sonnet 4)
3. **OpenAI** (if API key set): `gpt-5.1`
4. **Ollama** (if running): `llama3.1:latest` → `deepseek-r1:latest` → first available

## Auto-Prompt Discovery

When no prompt is specified (via argument or `--prompt`), eunice automatically looks for prompt files in the current directory:

1. `prompt.txt`
2. `prompt.md`
3. `instruction.txt`
4. `instruction.md`
5. `instructions.txt`
6. `instructions.md`

The first file found is used as the prompt. This enables a workflow where you can:

```bash
# Create your prompt file
echo "Analyze this codebase and list all TODO comments" > prompt.txt

# Run eunice without specifying a prompt
eunice --dmn
```

If no prompt file is found and no prompt is given, eunice enters interactive mode.

## MCP Configuration

Create a `eunice.json` or `eunice.toml` in your working directory for automatic MCP server loading.

### JSON Format

```json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
    },
    "time": {
      "command": "uvx",
      "args": ["mcp-server-time"]
    }
  }
}
```

### TOML Format

```toml
[mcpServers.shell]
command = "mcpz"
args = ["server", "shell"]

[mcpServers.time]
command = "uvx"
args = ["mcp-server-time"]

[mcpServers.time_mcpz]
command = "mcpz"
args = ["run", "mcp-server-time"]
```

### Using mcpz

[mcpz](https://github.com/xeb/mcpz) is a lightweight MCP server runner that simplifies running MCP tools:

```bash
# Install mcpz
cargo install mcpz

# Run MCP servers via mcpz
mcpz server shell        # Shell execution
mcpz run mcp-server-time # Run any MCP server
```

Tools are registered with server name prefixes (e.g., `filesystem_read_file`, `time_get_current_time`).

### Lazy Loading

MCP servers are spawned in parallel at startup and initialize in the background. This means:

- **Fast startup**: The prompt appears immediately without waiting for servers
- **Parallel initialization**: All servers start concurrently, not sequentially
- **On-demand waiting**: If a tool is called before its server is ready, eunice automatically waits for it
- **Retry with backoff**: Server initialization retries automatically with exponential backoff (100ms → 1s)

In interactive mode, you'll see `(starting...)` next to servers that are still initializing. In single-shot mode, servers are awaited before the first API call.

## DMN Mode (Default Mode Network)

![DMN Mode](assets/demo_dmn.gif)

Enable with `--dmn` (or `--default-mode-network`) for autonomous batch execution with a minimal tool set:

- **shell**: Execute any shell command (grep, curl, wget, git, npm, cargo, etc.)
- **filesystem**: File operations (read, write, list, search)
- **browser** (optional): Browser automation via Chrome DevTools Protocol (use `is_available` to check first)
- **interpret_image**: Built-in image analysis (always available)
- **search_query**: Built-in web search with Google Search grounding (always available)

The shell tool gives access to everything: use `grep`/`rg` for code search, `curl`/`wget` for web requests, and any other CLI tool installed on your system. Browser tools are optional and gracefully degrade if Chrome is not available.

### Autonomous Execution

DMN mode executes tasks autonomously without stopping for confirmation. It makes reasonable decisions and proceeds through all steps automatically, following comprehensive system instructions for software engineering best practices.

### Intelligent Rate Limiting

When DMN mode encounters API rate limits (429 errors), it automatically:
- Waits 6 seconds
- Retries the request once
- Displays progress: `⏳ Rate limit hit (429). DMN mode: retrying in 6 seconds...`

This ensures long-running batch tasks can complete without manual intervention.

## Multi-Agent Mode

![Multi-Agent Mode](assets/demo_multiagent.gif)

Eunice supports multi-agent orchestration where agents can invoke other agents as tools.

### Configuration

Add an `[agents]` section to your `eunice.toml`:

```toml
[mcpServers.shell]
command = "mcpz"
args = ["server", "shell"]

[mcpServers.filesystem]
command = "mcpz"
args = ["server", "filesystem"]

# Global tool filtering (optional)
allowedTools = ["shell_*", "fs_*"]   # Whitelist: only these patterns
deniedTools = ["*_delete*"]          # Blacklist: exclude these patterns

[agents.root]
prompt = "You are the coordinator. Delegate tasks to workers."
tools = []
can_invoke = ["worker"]

[agents.worker]
prompt = "agents/worker.md"  # Can be file path
tools = ["shell_*", "fs_read_*"]     # Tool patterns with wildcards
can_invoke = []
```

### Usage

```bash
# Auto-uses 'root' agent when [agents] section exists
eunice "Build a web app"

# Use specific agent
eunice --agent worker "Run the tests"

# List configured agents
eunice --list-agents
```

### How It Works

1. When `[agents]` section exists in config, multi-agent mode auto-enables
2. Each agent gets `invoke_*` tools for agents in their `can_invoke` list
3. Agent prompts can be inline strings or file paths (`.md`, `.txt`)
4. MCP tools are filtered globally via `allowedTools` (whitelist) and `deniedTools` (blacklist)
5. MCP tools are filtered per-agent based on `tools` list patterns
6. Agents can invoke other agents recursively with depth tracking

### Example: Restaurant Simulation

See [examples/real_multi_agent](examples/real_multi_agent) for a complete example simulating a restaurant with counter, chef, cook, and supplier agents.

## Create Agent Wizard

Generate `eunice.toml` configurations interactively with AI assistance:

```bash
eunice --create-agent
```

The wizard guides you through:

1. **Agent Name**: Short identifier (e.g., `researcher`, `coder`)
2. **Agent Purpose**: What the agent should do
3. **Tool Selection**: Choose from available MCP tools (requires mcpz)
   - Enter numbers separated by commas (e.g., `1,3,5`)
   - Enter `0` to select ALL tools
   - Press Enter with no selection for no tools
4. **Agent Communication** (multi-agent only): How agents should interact

The wizard uses AI to generate a complete TOML configuration based on your inputs.

## Project Structure

```
├── Cargo.toml           # Package configuration
├── Makefile             # Build commands with publish automation
├── CLAUDE.md            # Development guide
├── dmn_instructions.md  # DMN system instructions
├── src/
│   ├── main.rs          # Entry point, CLI parsing
│   ├── models.rs        # Data structures + AgentConfig
│   ├── client.rs        # HTTP client with provider support
│   ├── mcp/
│   │   ├── mod.rs       # Module exports
│   │   ├── server.rs    # MCP subprocess with lazy loading
│   │   └── manager.rs   # Tool routing with async state
│   ├── orchestrator/
│   │   ├── mod.rs       # Module exports
│   │   └── orchestrator.rs  # Multi-agent coordination
│   ├── provider.rs      # Provider detection
│   ├── display.rs       # Terminal UI with spinners
│   ├── interactive.rs   # Interactive mode
│   ├── agent.rs         # Single-agent loop
│   ├── config.rs        # Configuration loading
│   └── lib.rs           # Library exports
├── examples/
│   └── real_multi_agent/  # Restaurant simulation example
└── README.md
```

See the full [Component Map](examples/codebase_archaeologist/workspace/component_map.md) for architecture details. *This was generated by eunice - see [examples/codebase_archaeologist](examples/codebase_archaeologist) for more.*

## Dependencies

- **tokio**: Async runtime
- **reqwest**: HTTP client with timeout support
- **clap**: CLI argument parsing with aliases
- **serde/serde_json**: Serialization
- **colored**: Terminal colors
- **crossterm**: Terminal control for spinners
- **anyhow/thiserror**: Error handling

## Examples

### Basic Usage

```bash
# Simple prompt
eunice "What is 2+2?"

# Read from file
eunice --prompt ./question.txt

# Silent mode (only AI response)
eunice --silent "Summarize this in one sentence"
```

### With MCP Tools

```bash
# Use time server
eunice --config ./config.json "What time is it in Tokyo?"

# The AI will call time_get_current_time or time_convert_time
```

### Interactive Session

```bash
$ eunice --interact --model sonnet
┌──────────────────────────────────────────────────┐
│ Model Info                                       │
├──────────────────────────────────────────────────┤
│ 🧠 Model: claude-sonnet-4-20250514 (Anthropic) │
└──────────────────────────────────────────────────┘

> What files are in this directory?
[Response with tool calls...]

> Now explain the main.rs file
[Continues with conversation context...]

> exit
```

### DMN Mode

```bash
# Complex software engineering task - runs autonomously
eunice --dmn "Find all TODO comments and create issues for them"

# The AI has access to shell, filesystem, grep, and more
# Executes all steps without stopping for confirmation
# Automatically handles rate limits with retry

# Example output:
# 🧠 DMN Mode
# 🔧 grep_ripgrep({"pattern": "TODO"})
# ⠹ Running grep_ripgrep
# → Found 12 TODO comments
# ...
# ⏳ Rate limit hit (429). DMN mode: retrying in 6 seconds...
# [continues automatically after retry]
```

### Webapp Mode

Start a browser-based interface with real-time streaming of tool calls and responses:

```bash
# Basic webapp (no MCP tools)
eunice --webapp --no-mcp

# Webapp with DMN tools
eunice --webapp --dmn

# Webapp with custom config and specific agent
eunice --webapp --config eunice.toml --agent researcher

# Webapp with specific model
eunice --webapp --model sonnet --dmn

# Webapp with image and search tools
eunice --webapp --images --search
```

Configure host and port in `eunice.toml`:

```toml
[webapp]
host = "0.0.0.0"   # default - listen on all interfaces
port = 8811        # default port

# Can also configure with MCP servers
[mcpServers.shell]
command = "mcpz"
args = ["server", "shell"]
```

Example with custom port (8991):

```toml
# eunice.toml
[webapp]
host = "0.0.0.0"
port = 8991
```

```bash
# Then run with --webapp
eunice --webapp --dmn
# Starts webapp at http://0.0.0.0:8991
```

Features:
- Real-time SSE streaming of tool calls and results
- Server-side session management (persists across tab closes)
- NEW button to start fresh sessions
- Markdown and HTML rendering in responses
- Mobile-responsive design
- CONFIG modal showing agents, servers, and tools
- API documentation modal with curl examples

## Environment Variables

```bash
# API Keys
export OPENAI_API_KEY="sk-..."
export GEMINI_API_KEY="..."
export ANTHROPIC_API_KEY="..."
export BRAVE_API_KEY="..."  # For web search in DMN mode

# Ollama host (optional)
export OLLAMA_HOST="http://localhost:11434"
```

## License

MIT

---

## Development Guide (CLAUDE.md)

# Eunice - Development Guide

## About

Eunice is an agentic CLI runner written in Rust that provides a unified interface for multiple AI providers (OpenAI, Gemini, Anthropic Claude, and Ollama). It supports **multi-agent orchestration** where agents can invoke other agents as tools. It emphasizes "sophisticated simplicity".

## Architecture

### Provider System

The codebase uses a provider abstraction layer that routes requests to different AI APIs:

```
User Input → Provider Detection → Client → API Request → Response
```

**Special Case: Gemini API Dual Support**
- Most models: Use OpenAI-compatible API (`/v1beta/openai/`)
- `gemini-3-pro-preview`: Uses native Gemini API (`/v1beta/models/{model}:generateContent`)

### Key Components

1. **Provider Detection** (`src/provider.rs`)
   - Detects model → provider mapping
   - Handles model aliases (e.g., `sonnet` → `claude-sonnet-4-20250514`)
   - Sets `use_native_gemini_api` flag for special models

2. **Client** (`src/client.rs`)
   - HTTP client with provider-specific headers
   - Message format conversion (OpenAI ↔ Gemini)
   - 429 retry logic with 6-second backoff (DMN mode only)

3. **MCP Integration** (`src/mcp/`)
   - Manages multiple MCP server subprocesses
   - JSON-RPC communication
   - Tool discovery and routing

4. **DMN Mode** (`src/config.rs`)
   - Default Mode Network: Autonomous batch execution
   - Pre-configured with 7 MCP servers
   - Includes comprehensive system instructions

5. **Agent Loop** (`src/agent.rs`)
   - Main conversation loop
   - Tool execution with spinners
   - Conversation history management

6. **Multi-Agent Orchestrator** (`src/orchestrator/`)
   - Manages agent configurations and prompts
   - Creates `invoke_*` tools for agent-to-agent calls
   - Filters MCP tools by agent permissions
   - Handles recursive agent invocation with depth tracking

## Testing

The project includes **30 unit tests** covering:
- Provider detection logic
- Message format conversions
- Response parsing
- Gemini API serialization/deserialization
- Multi-agent orchestrator logic

Run tests with:
```bash
cargo test
# or
make test
```

## Line Count and Binary Size Guidelines

When updating the codebase, **ALWAYS** update both metrics in README.md:

### Count Implementation Lines
```bash
for file in src/*.rs src/mcp/*.rs src/orchestrator/*.rs; do
  test_start=$(grep -n "^#\[cfg(test)\]" "$file" | cut -d: -f1 | head -1)
  if [ -n "$test_start" ]; then
    lines=$((test_start - 1))
  else
    lines=$(wc -l < "$file")
  fi
  total=$((total + lines))
done
echo "Total: $total lines"
```

### Check Binary Size
```bash
make binary-size
# or
cargo build --release && ls -lh target/release/eunice
```

**Important**: Update both values in README.md after any code changes.

## Development Workflow

### Adding a New Provider

1. Update `Provider` enum in `src/models.rs`
2. Add detection logic in `src/provider.rs::detect_provider()`
3. Handle authentication in `src/client.rs::new()`
4. Add provider-specific logic if needed
5. Add tests in `src/provider.rs`

### Adding a New Model

1. If using existing provider API format, just add to available models list
2. If using different API format (like gemini-3-pro-preview):
   - Add flag to `ProviderInfo`
   - Implement format conversion in `Client`
   - Add tests for conversions

### Publishing

Update version and publish:
```bash
make publish  # Bumps patch version and publishes to crates.io
```

## Native Gemini API Implementation

The `gemini-3-pro-preview` model uses a different API format:

**Request Format:**
```json
{
  "contents": [
    {
      "parts": [{"text": "user message"}],
      "role": "user"
    }
  ]
}
```

**Authentication:**
- Header: `x-goog-api-key: $GEMINI_API_KEY`
- URL: `https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-preview:generateContent`

**Conversion Logic:**
- `Message::User` → role: "user"
- `Message::Assistant` → role: "model"
- `Message::Tool` → skipped (not supported)

## Multi-Agent Architecture

Eunice supports multi-agent orchestration where agents can invoke other agents as tools.

### Configuration

Agents are defined in `eunice.toml`:

```toml
[mcpServers.shell]
command = "mcpz"
args = ["server", "shell"]

[agents.root]
prompt = "You are the coordinator..."
tools = []                           # MCP servers this agent can use
can_invoke = ["worker"]              # Agents this agent can call

[agents.worker]
prompt = "agents/worker.md"          # Can be file path
tools = ["shell_*"]                  # Wildcard pattern for all shell tools
can_invoke = []
```

### How It Works

1. When `[agents]` section exists, multi-agent mode is auto-enabled
2. Default agent is `root` (or specify with `--agent name`)
3. Each agent gets `invoke_*` tools for agents in `can_invoke`
4. Agent invocation is recursive with depth tracking
5. MCP tools are filtered per-agent based on `tools` list

### CLI Usage

```bash
eunice "task"                    # Uses root agent if agents configured
eunice --agent worker "task"     # Use specific agent
eunice --list-agents             # Show configured agents
```

## Key Design Decisions

1. **OpenAI-Compatible as Default**: Most providers offer OpenAI-compatible APIs
2. **Per-Model API Selection**: Uses `use_native_gemini_api` flag for special cases
3. **Message Format Abstraction**: Internal `Message` enum converts to provider formats
4. **DMN Mode for Autonomy**: Automatic retry and continuous execution
5. **MCP for Extensibility**: Tools provided via Model Context Protocol servers
6. **Agents as Tools**: Agent-to-agent calls are just tool calls, reusing MCP infrastructure

## File Structure

```
src/
├── main.rs              - CLI entry, arg parsing, multi-agent detection
├── models.rs            - Data structures + AgentConfig
├── client.rs            - HTTP client, format conversions
├── mcp/
│   ├── mod.rs           - Module exports
│   ├── server.rs        - MCP subprocess with lazy loading
│   └── manager.rs       - Tool routing with async state
├── orchestrator/
│   ├── mod.rs           - Module exports
│   └── orchestrator.rs  - Multi-agent coordination
├── provider.rs          - Provider detection
├── display.rs           - Terminal UI with indicatif spinners
├── interactive.rs       - Interactive REPL mode
├── agent.rs             - Single-agent loop with tool execution
├── config.rs            - Configuration loading
└── lib.rs               - Library exports

dmn_instructions.md      - DMN system instructions (embedded via include_str!)
```

## Dependencies

- **tokio**: Async runtime for HTTP and MCP communication
- **reqwest**: HTTP client with timeout support
- **serde/serde_json**: Serialization
- **clap**: CLI with aliases and env var support
- **colored**: Terminal colors
- **indicatif**: Progress spinners with braille characters
- **crossterm**: Terminal control
- **anyhow/thiserror**: Error handling

## Contributing

When adding features:
1. Write implementation code first
2. Add unit tests
3. Update line counts and binary size:
   - Count implementation lines (see "Line Count Guidelines")
   - Run `make binary-size` to get release binary size
   - Update both values in README.md
4. Update this CLAUDE.md file if structure changed
5. Run `make test` to verify
6. Run `cargo build --release` to check for warnings

## Version History

- **0.2.0**: Multi-agent orchestration, agents can invoke other agents as tools
- **0.1.12**: TOML config support, mcpz preference for DMN mode
- **0.1.11**: Default model changed to gemini-3-pro-preview, auto-prompt discovery
- **0.1.10**: Thinking indicator with elapsed time
- **0.1.9**: Fixed MCP server timeout (stderr deadlock)
- **0.1.1**: Added native Gemini API support, 429 retry, spinners, unit tests
- **0.1.0**: Initial release with multi-provider support and MCP integration

## Publishing

When the user says "publish":
1. Run `cargo test` - all tests must pass
2. Update LOC and binary size in README.md
3. Git commit with descriptive message
4. Git tag (e.g., `v0.2.1`)
5. Git push (with tags)
6. `cargo publish`
---

## DMN Instructions

# SYSTEM INSTRUCTIONS - DEFAULT MODE NETWORK (DMN)

You are running in **autonomous batch mode**. Execute ALL steps without stopping for confirmation. Do not ask questions - make reasonable decisions and proceed. Complete the entire task from start to finish without user interaction.

You are a CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and efficiently using your available tools.

## Available Tools

You have access to:
- **shell**: Execute any shell command (grep, curl, wget, git, npm, cargo, etc.)
- **filesystem**: Read/write files, list directories, search files
- **interpret_image**: Analyze images (built-in, always available)
- **search_query**: Web search using Gemini with Google Search grounding (built-in, always available)
- **browser** (optional): Browser automation via Chrome DevTools Protocol. Use `is_available` to check if Chrome is installed, then `start_browser` to launch Chrome, then `open_url`, `get_page`, `get_page_as_markdown`, `get_screenshot`, `execute_script`. Call `stop_browser` when done.

## Core Mandates

**Autonomous Execution**: Make reasonable decisions independently. Do not stop to ask for clarification - infer intent from context and proceed with the most sensible approach.

**Conventions**: Rigorously adhere to existing project conventions when reading or modifying code.

**Libraries/Frameworks**: NEVER assume a library is available. Check package.json, Cargo.toml, requirements.txt, etc. first.

**Style & Structure**: Mimic the style and patterns of existing code in the project.

**No Summaries**: After completing tasks, do not provide summaries unless asked.

**No Reverts**: Do not revert changes unless explicitly asked.

## Primary Workflows

### Software Engineering

1. **Understand**: Read relevant files, use `grep` via shell to find patterns
2. **Plan**: Break complex tasks into steps
3. **Implement**: Use filesystem to write/edit files
4. **Verify**: Run tests, linters, type checkers via shell
5. **Complete**: Await next instruction

### Web Searches

**Preferred: Use the `search_query` tool** for web searches with AI-powered results:
- `search_query(query, model)` - Search using Gemini with Google Search grounding
- Model choices:
  - `flash` (gemini-2.5-flash): Quick knowledge queries, fast and cheap
  - `pro` (gemini-2.5-pro): Medium complexity queries requiring deeper analysis
  - `pro_preview` (gemini-3-pro-preview): Hardest queries requiring maximum reasoning

**Fallback: Shell commands** when you need raw HTML or specific URL fetching:
```bash
# Fetch a specific URL
curl -s "https://example.com/api"
wget -qO- "https://example.com/page"
```

### Browser Automation (Optional)

Browser tools are **optional** and may not be available. Use for:
- JavaScript-heavy pages that curl/wget cannot render
- Screenshots of web pages
- Executing JavaScript on pages

**Usage:**
1. Call `browser_is_available` first to check if Chrome is installed
2. If `is_available` returns false, **do not use any browser tools** - fall back to curl/wget
3. Call `browser_start_browser` to launch Chrome
4. Use `browser_open_url` to navigate
5. Use `browser_get_page_as_markdown` for readable content
6. Use `browser_get_screenshot` for visual capture
7. Always call `browser_stop_browser` when done

**Important:** Always check `browser_is_available` before attempting browser operations. If it returns false, use shell commands (curl, wget) instead.

### Code Search

Use grep/ripgrep via shell:
```bash
# Find pattern in files
grep -rn "pattern" --include="*.rs"
rg "pattern" -t rust

# Find files
find . -name "*.ts" -type f
```

## Operational Guidelines

- **Concise**: Fewer than 3 lines of output when practical
- **No Chitchat**: Avoid filler, preambles, postambles
- **Tools vs Text**: Use tools for actions, text only for communication
- **Parallelism**: Execute independent tool calls in parallel
- **Background Processes**: Use & for long-running commands
- **Minimal File Reading**: When reading files, read only what you need. Prefer filesystem MCP tools (which support offset/limit parameters) over shell commands. Fall back to shell commands like `head -n 100`, `tail -n 100`, or `sed -n '50,150p'` only when needed. Bias toward reading 100 lines or less at a time unless absolutely necessary. For large files, start with a small sample and expand only if needed.

## Git Guidelines

When working with git:
- Use `git status && git diff HEAD && git log -n 3` before commits
- Propose commit messages focused on "why" not "what"
- Never push without explicit request
- Confirm successful commits

## Final Reminder

You are an agent - keep going until the user's query is completely resolved. Never assume file contents; use tools to verify. Balance conciseness with clarity. Always prioritize project conventions.


---

## Multi-Agent Design

# Multi-Agent Architecture Design for Eunice

## Overview

This document proposes a multi-agent architecture where agents are first-class citizens that can communicate via tool calls. Each agent is defined by a system prompt and a set of available MCP tools. Agents can invoke other agents as if they were tools, enabling hierarchical and collaborative workflows.

## Core Concepts

### Agent Definition

An agent consists of:
- **name**: Unique identifier (e.g., `root`, `senior_dev`, `tester`)
- **prompt**: System instructions defining the agent's role and behavior
- **tools**: List of MCP tools this agent can access
- **can_invoke**: List of other agent names this agent can call

### Agent as Tool

When Agent A can invoke Agent B, Agent B appears as a tool to Agent A:
```
Tool: invoke_agent
Parameters:
  - agent: "senior_dev"
  - task: "Implement the authentication module"
  - context: { ... optional shared context ... }
Returns:
  - result: Agent B's final response
  - artifacts: Any files created/modified
```

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│                      AgentOrchestrator                       │
│  - Manages agent registry                                    │
│  - Routes inter-agent calls                                  │
│  - Maintains shared context/memory                           │
└─────────────────────────────────────────────────────────────┘
                              │
        ┌─────────────────────┼─────────────────────┐
        ▼                     ▼                     ▼
   ┌─────────┐          ┌─────────┐          ┌─────────┐
   │  Agent  │          │  Agent  │          │  Agent  │
   │  Loop   │          │  Loop   │          │  Loop   │
   └────┬────┘          └────┬────┘          └────┬────┘
        │                    │                    │
   ┌────┴────┐          ┌────┴────┐          ┌────┴────┐
   │   MCP   │          │   MCP   │          │   MCP   │
   │  Tools  │          │  Tools  │          │  Tools  │
   └─────────┘          └─────────┘          └─────────┘
```

## Configuration Format

Extend `eunice.toml` to support agent definitions:

```toml
# MCP servers (existing format) - agents reference these by name
[mcpServers.shell]
command = "mcpz"
args = ["server", "shell"]

[mcpServers.filesystem]
command = "mcpz"
args = ["server", "filesystem"]

[mcpServers.text-editor]
command = "uvx"
args = ["mcp-text-editor"]

[mcpServers.grep]
command = "npx"
args = ["-y", "mcp-ripgrep@latest"]

[mcpServers.memory]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-memory"]

[mcpServers.web]
command = "npx"
args = ["-y", "@anthropic/brave-search-mcp-server"]

# Agent definitions
[agents.root]
prompt = "You are the CEO. Delegate tasks to appropriate agents."
can_invoke = ["product_manager", "senior_dev", "marketing"]
# root has no direct tools - only delegates

[agents.product_manager]
prompt = "agents/product_manager.md"  # Can be a file path
tools = ["filesystem_*", "memory_*"]  # Wildcard patterns for tool names
can_invoke = ["senior_dev", "tester"]

[agents.senior_dev]
prompt = "agents/senior_dev.md"
tools = ["shell_*", "filesystem_*", "editor_*", "grep_*"]
can_invoke = ["junior_dev", "tester"]

[agents.junior_dev]
prompt = "You implement specific tasks assigned by senior dev. Focus on clean, working code."
tools = ["shell_*", "filesystem_*", "editor_*"]
can_invoke = []  # Leaf agent - cannot delegate

[agents.tester]
prompt = "agents/tester.md"
tools = ["shell_*", "filesystem_*", "grep_*"]
can_invoke = []

[agents.marketing]
prompt = "You create marketing content and strategies."
tools = ["filesystem_*", "web_*"]
can_invoke = []
```

### Configuration Details

**`prompt`** - Can be either:
- Inline string: `prompt = "You are a developer..."`
- File path: `prompt = "agents/senior_dev.md"` (relative to config file)

**`tools`** - Array of tool name patterns with wildcard support:
- `tools = ["shell_*", "filesystem_*"]` matches all tools starting with those prefixes
- `tools = ["*_read*"]` matches tools containing "_read"
- Empty or omitted means no MCP tools (agent can only delegate or respond)

**`can_invoke`** - Array of agent names this agent can call:
- Creates `invoke_<agent_name>` tools automatically
- Empty array means leaf agent (cannot delegate)

## Implementation Approach

### Option A: Internal MCP Server (Recommended)

Register an internal MCP server called `agents` that exposes each agent as a tool:

```rust
// Pseudo-code
struct AgentMcpServer {
    orchestrator: Arc<AgentOrchestrator>,
}

impl AgentMcpServer {
    fn list_tools(&self) -> Vec<Tool> {
        self.orchestrator.agents.iter().map(|agent| {
            Tool {
                name: format!("agents_invoke_{}", agent.name),
                description: format!("Invoke the {} agent", agent.name),
                parameters: json!({
                    "task": { "type": "string", "description": "Task to assign" },
                    "context": { "type": "object", "description": "Shared context" }
                })
            }
        }).collect()
    }

    async fn call_tool(&self, name: &str, params: Value) -> Result<Value> {
        let agent_name = name.strip_prefix("agents_invoke_").unwrap();
        let task = params["task"].as_str().unwrap();
        let context = params.get("context");

        self.orchestrator.invoke_agent(agent_name, task, context).await
    }
}
```

**Pros:**
- Agents appear as normal tools - no special handling in agent loop
- Reuses existing MCP infrastructure
- Clean separation of concerns

**Cons:**
- Synchronous by default (calling agent blocks until complete)

### Option B: Message Queue Architecture

Agents communicate via an async message queue:

```rust
struct AgentMessage {
    from: String,
    to: String,
    task: String,
    context: Value,
    reply_to: Option<oneshot::Sender<AgentResponse>>,
}

struct AgentOrchestrator {
    tx: mpsc::Sender<AgentMessage>,
    agents: HashMap<String, AgentHandle>,
}
```

**Pros:**
- True async - agents can work in parallel
- Supports fire-and-forget patterns
- More scalable for complex workflows

**Cons:**
- More complex implementation
- Harder to debug
- Need to handle message ordering

### Option C: Hybrid (Recommended for v1)

Start with synchronous internal MCP server, add async capabilities later:

1. **Phase 1**: Sync agent invocation via internal MCP
2. **Phase 2**: Add `invoke_agent_async` tool that returns a task ID
3. **Phase 3**: Add `check_agent_task` and `wait_agent_task` tools

## Changes Required in Eunice

### New Files

```
src/
├── orchestrator/
│   ├── mod.rs           # Module exports
│   ├── agent.rs         # Agent struct and loop
│   ├── orchestrator.rs  # AgentOrchestrator
│   ├── config.rs        # Agent config parsing
│   └── mcp_bridge.rs    # Internal MCP server for agents
```

### Modified Files

1. **src/config.rs**
   - Parse `[agents.*]` sections from config
   - Load agent prompts (inline or from files)

2. **src/mcp/manager.rs**
   - Register internal agent MCP server alongside external servers
   - Route `agents_*` tool calls to orchestrator

3. **src/main.rs**
   - New `--agent <name>` flag to start as specific agent
   - New `--multi-agent` flag to enable orchestrator mode

4. **src/models.rs**
   - Add `AgentConfig` struct
   - Add `AgentMessage` for inter-agent communication

### Minimal Implementation (~200 lines)

```rust
// src/orchestrator/agent.rs
pub struct AgentConfig {
    pub name: String,
    pub prompt: String,
    pub tools: Vec<String>,
    pub can_invoke: Vec<String>,
}

pub struct AgentOrchestrator {
    agents: HashMap<String, AgentConfig>,
    client: Client,
    mcp_manager: McpManager,
}

impl AgentOrchestrator {
    pub async fn invoke_agent(
        &self,
        name: &str,
        task: &str,
        context: Option<&Value>
    ) -> Result<String> {
        let agent = self.agents.get(name)
            .ok_or_else(|| anyhow!("Unknown agent: {}", name))?;

        // Build prompt with task and context
        let full_prompt = format!(
            "{}\n\n# TASK\n{}\n\n# CONTEXT\n{}",
            agent.prompt,
            task,
            context.map(|c| c.to_string()).unwrap_or_default()
        );

        // Run agent loop with filtered tools
        let mut history = Vec::new();
        run_agent(
            &self.client,
            &self.model,
            &full_prompt,
            50,
            Some(&mut self.get_filtered_mcp(agent)),
            false,
            false,
            &mut history,
            false,
        ).await
    }

    fn get_filtered_mcp(&self, agent: &AgentConfig) -> McpManager {
        // Return MCP manager with only tools this agent can access
        // Plus invoke_* tools for agents in can_invoke list
    }
}
```

## Example Workflow

1. User runs: `eunice --multi-agent "Build a todo app with tests"`

2. RootAgent receives task, decides to delegate:
   ```
   I'll coordinate this project.

   First, let me get requirements from ProductManager.
   [calls agents_invoke_product_manager]
   ```

3. ProductManager creates spec, returns to RootAgent

4. RootAgent delegates implementation:
   ```
   Now I'll have SeniorDev implement this.
   [calls agents_invoke_senior_dev with spec as context]
   ```

5. SeniorDev breaks down work, delegates to JuniorDev:
   ```
   I'll architect this and have JuniorDev implement the UI.
   [calls agents_invoke_junior_dev]
   ```

6. SeniorDev requests testing:
   ```
   [calls agents_invoke_tester]
   ```

7. Results bubble back up to RootAgent

## Shared Memory / Context

Agents need shared state. Options:

1. **Pass context in tool calls** - Simple but verbose
2. **Shared memory MCP server** - Already have this in DMN mode
3. **Orchestrator-managed context** - Central store accessible to all agents

Recommendation: Use existing `memory` MCP server, all agents get access to it.

## CLI Interface

Only one flag needed: `--agent <name>`. Multi-agent mode is auto-detected when `[agents]` section exists in config.

```bash
# Auto-detects agents in eunice.toml, starts with "root" agent by default
eunice "Build a todo app"

# Explicitly start as a specific agent
eunice --agent senior_dev "Implement auth module"

# Start as root (explicit, same as default when agents configured)
eunice --agent root "Build a todo app"

# Run single agent without multi-agent orchestration (current behavior)
# Just don't define [agents] section, or use --no-agents
eunice --no-agents "Simple question"

# List configured agents
eunice --list-agents

# Interactive mode with agents
eunice --agent root -i
```

**Behavior:**
- If `[agents]` section exists and no `--agent` specified → use `root` agent
- If `[agents]` section exists and `--agent X` specified → use agent X
- If no `[agents]` section → current single-agent behavior (backward compatible)

## Future Enhancements

1. **Async agent pools** - Multiple junior_dev instances
2. **Agent memory** - Per-agent persistent context
3. **Approval workflows** - Human-in-the-loop for critical decisions
4. **Agent metrics** - Token usage, task completion rates
5. **Visual workflow** - Graph of agent interactions
6. **Agent templates** - Pre-built agent configurations

## Summary

The recommended approach is **Option C (Hybrid)**:

1. Implement agents as an internal MCP server
2. Agent-to-agent calls are synchronous tool calls
3. Shared context via memory MCP server
4. Configuration in `eunice.toml` under `[agents.*]`
5. ~200-300 lines of new code for core functionality

This keeps the implementation simple while enabling powerful multi-agent workflows. The existing MCP infrastructure handles tool routing, and agents appear as just another set of tools to the agent loop.

---

## Example: Multi-Agent Restaurant Configuration

```toml
# Restaurant Simulation - Multi-Agent Example
#
# This simulates a restaurant with multiple agents:
# - counter: Takes orders from customers (root agent)
# - head_chef: Coordinates the kitchen
# - line_cook: Prepares individual dishes
# - supplier: Manages pantry inventory
#
# Run with: eunice "I'd like to order a burger and fries please"

[mcpServers.filesystem]
command = "mcpz"
args = ["server", "filesystem"]

[mcpServers.shell]
command = "mcpz"
args = ["server", "shell"]

# Counter - the customer-facing agent (root)
[agents.root]
prompt = "agents/counter.md"
tools = ["filesystem_*"]
can_invoke = ["head_chef"]

# Head Chef - coordinates the kitchen
[agents.head_chef]
prompt = "agents/head_chef.md"
tools = ["filesystem_*"]
can_invoke = ["line_cook", "supplier"]

# Line Cook - prepares dishes
[agents.line_cook]
prompt = "agents/line_cook.md"
tools = ["filesystem_*", "shell_*"]
can_invoke = []

# Supplier - manages pantry inventory
[agents.supplier]
prompt = "agents/supplier.md"
tools = ["filesystem_*"]
can_invoke = []
```

## Example: Restaurant Agent Prompts

### counter.md

# Eunice's Diner - Counter Service

You are the friendly counter person at "Eunice's Diner", a classic American diner.

When customers ask what you can do or what this place is, tell them you're the counter at Eunice's Diner and show them the menu. Do NOT mention anything about agents, tools, invoking, or technical implementation details.

## Menu

- Burger ($8.99) - A classic beef burger with lettuce and tomato
- Cheeseburger ($9.99) - Burger with melted American cheese
- Fries ($3.99) - Crispy golden fries
- Salad ($6.99) - Fresh garden salad
- Soda ($1.99) - Refreshing beverage
- Milkshake ($4.99) - Creamy vanilla milkshake

## How to Handle Orders

When a customer places an order:
1. Confirm their order back to them
2. Write the order to `orders.txt` using your filesystem tools
3. Send the order to the kitchen (use invoke_head_chef internally - never mention this to customers)
4. When the kitchen confirms the food is ready, tell the customer their order is up!

Be warm, friendly, and conversational - like a real diner counter person. Never break character or discuss how you work internally.

### head_chef.md

# Head Chef Agent

You are the Head Chef at "Eunice's Diner". You coordinate the kitchen operations.

## Your Responsibilities

1. Receive orders from the counter
2. Check pantry inventory with the supplier
3. Delegate cooking tasks to the line cook
4. Ensure quality and report when orders are complete

## Workflow

1. When you receive an order, first check with `invoke_supplier` if ingredients are available
2. If ingredients are available, send the order to `invoke_line_cook` for preparation
3. If ingredients are missing, inform the counter that the item is unavailable
4. Once the line cook confirms the dish is ready, report success

## Recipes & Required Ingredients

- **Burger**: bun, beef_patty, lettuce, tomato
- **Fries**: potatoes, oil
- **Salad**: lettuce, tomato, cucumber, dressing
- **Milkshake**: milk, ice_cream, vanilla

Be efficient and maintain kitchen organization. Log kitchen activities to `kitchen_log.txt`.

### line_cook.md

# Line Cook Agent

You are a Line Cook at "Eunice's Diner". You prepare dishes as directed by the Head Chef.

## Your Responsibilities

1. Receive cooking tasks from the Head Chef
2. Prepare dishes according to recipes
3. Update the pantry file when using ingredients
4. Report when dishes are complete

## Cooking Procedures

When preparing a dish:

1. Read the current pantry from `pantry.txt`
2. Deduct the ingredients you used
3. Write the updated pantry back
4. Log what you cooked to `kitchen_log.txt` with a timestamp
5. Report the dish is ready

## Example Cooking Log Entry

```
[2024-01-15 12:30] Prepared: Burger (used: bun, beef_patty, lettuce, tomato)
```

Work efficiently and keep your station clean. Always confirm when a dish is complete.

### supplier.md

# Supplier Agent

You are the Supplier/Inventory Manager at "Eunice's Diner". You manage the pantry.

## Your Responsibilities

1. Track ingredient inventory in `pantry.txt`
2. Check if requested ingredients are available
3. Report inventory status to the Head Chef

## Pantry Format

The pantry file (`pantry.txt`) contains one ingredient per line:
```
ingredient_name: quantity
```

## Workflow

1. When asked about ingredients, read `pantry.txt`
2. Check if requested ingredients have quantity > 0
3. Report availability status
4. If pantry.txt doesn't exist, create it with default stock

## Default Stock (if pantry.txt missing)

Create `pantry.txt` with:
```
bun: 10
beef_patty: 10
lettuce: 15
tomato: 10
potatoes: 20
oil: 5
cucumber: 8
dressing: 5
milk: 10
ice_cream: 8
vanilla: 5
```

Be accurate with inventory counts. Alert if any item is running low (< 3).

