# Agenvoy
> Docs for Agenvoy, the open-source local AI agent for Claude Code, Codex and Ollama users: install, models, MCP server, AI tools, memory and security.
Every documentation page in navigation order; each section starts with its source URL.
---
Source: https://agenvoy.com/docs/index.md
Make AI do the work on your computer—not just talk about it
Agenvoy is an open-source, self-hosted 24/7 personal AI agent that runs on your own computer — your memory, schedules, tools and credentials stay local.
From live research and file work to automation, it takes action and delivers results in a single Go binary; through MCP, it shares sandboxed tools with Claude Code, Codex, and other agents.
## Taiwan-developed AI Agent Harness
Agenvoy is a Taiwan-developed AI Agent Harness by [Pardn Chiu](https://pardn.io/), built to turn conversation into completed work on your computer. It coordinates models, context, tools, real-time data, task routing, memory, schedules, and execution in one workflow, while keeping control of your files and environment in your hands. Through the Web interface, it also supports hands-free voice interaction with natural speech, wake-word detection, and interruptible spoken replies; the full result remains available in the chat.
## Why Agenvoy
A chat can give you an answer; work needs a result. Agenvoy breaks requests into steps, calls tools, and delivers outcomes on your computer—while you retain control of files, tools, schedules, and working context.
- **Turns conversation into deliverable work** — Research live data, organize files, and complete multi-step tasks with an agent that acts and reports the result.
- **Fills capability gaps itself** — Creates, tests, and keeps a new tool when no suitable one exists, ready to reuse next time.
- **Shares one tool library across agents** — Agenvoy, Claude Code, Codex, and other agents use the same sandboxed tools instead of rebuilding them.
- **Keeps automation running** — Create schedules in one sentence; recurring work runs in your environment and pushes the result.
- **Makes every step visible and controllable** — Command output streams to the TUI and Web dashboard, while sensitive paths and restricted actions still require confirmation.
- **Connects models and external services freely** — Route models by task and configure image generation, STT, TTS, stdio/HTTP MCP servers, and OAuth.
- **Provides private access from anywhere** — The local daemon connects outward to Telegram and Discord, without making your host public or opening inbound ports.
## Validated designs
Agenvoy has already implemented the design directions below, and other teams have since adopted similar approaches, confirming that these directions solve real problems agents face in practice. Every milestone links to a public commit or release, so you can check it yourself.
| Shipped in Agenvoy | Design | Same direction elsewhere |
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 2025-06-28 [Implement core application](https://github.com/agenvoy/cim-prototype/commit/df4fa95a01) 2025-06-29 [add traditional memory structure](https://github.com/agenvoy/cim-prototype/commit/d9a109c270) | Summary-based memory for unbounded context | 2025-11-24 [Claude client-side compaction](https://platform.claude.com/docs/en/release-notes/overview#november-24-2025) |
| 2026-01-04 [bubblewrap sandbox](https://github.com/pardnchiu/HakoRun/commit/87af1ba486) 2026-02-01 [harden bubblewrap sandbox](https://github.com/pardnchiu/HakoRun/commit/0cae0ed8c5) 2026-03-07 [block sensitive paths and credentials](https://github.com/pardnchiu/Agenvoy/commit/fdaaa62b3b) 2026-03-18 [sandbox execution with bubblewrap](https://github.com/pardnchiu/Agenvoy/commit/1f458d3596) | Sandbox mode for secure agents | 2026-03-16 [NVIDIA NemoClaw](https://blogs.nvidia.com.tw/blog/nvidia-announces-nemoclaw) |
| 2026-02-07 [Copilot device code login](https://github.com/pardnchiu/Agenvoy/commit/3d4020dfb2) 2026-04-04 [OpenAI Codex (OAuth) provider](https://github.com/pardnchiu/Agenvoy/commit/748b551ce1) | Sign in with an existing AI subscription | 2026-09-29 [Pi 0.99.0 Sign in with ChatGPT](https://github.com/earendil-works/pi/blob/v0.99.0/packages/coding-agent/docs/providers.md#authenticate-interactively) |
| 2026-02-27 [LLM-driven agent routing](https://github.com/pardnchiu/Agenvoy/commit/0514cc1127) 2026-03-10 [configurable dispatcher model selection](https://github.com/pardnchiu/Agenvoy/commit/e4274818e7) 2026-04-15 [strengthen agent tier routing](https://github.com/pardnchiu/Agenvoy/commit/055af67e44) 2026-06-10 [tiered routing across providers](https://github.com/pardnchiu/Agenvoy/commit/d3c5d9f63a) 2026-09-11 [configurable model tiers for agent and subagent routing](https://github.com/pardnchiu/Agenvoy/commit/2032737d23) 2026-09-11 [model tier controls on fallback priority](https://github.com/pardnchiu/Agenvoy/commit/b393d22f12) | Route each task to the model best suited to it | 2026-09-14 [Copilot auto model selection tiers: efficiency / balance / intelligence](https://github.blog/changelog/2026-09-14-configure-cost-and-quality-in-copilot-auto-model-selection/) 2026-09-29 [Pi 0.99.0 virtual models: per-request model and thinking level](https://github.com/earendil-works/pi/blob/v0.99.0/packages/coding-agent/docs/virtual-models.md) 2026-09-29 [Pi 0.99.0 classifier models](https://github.com/earendil-works/pi/blob/v0.99.0/packages/coding-agent/docs/llama-cpp.md#classification) |
| 2026-03-23 [script tool support with sandboxed execution](https://github.com/pardnchiu/Agenvoy/commit/eb789ef4be) 2026-05-14 [script tool scaffolding skill](https://github.com/pardnchiu/Agenvoy/commit/33a62c9161) 2026-06-05 [script tool runtime metadata](https://github.com/pardnchiu/Agenvoy/commit/74ac5bf7eb) | Custom JS / Python tools run in the sandbox | 2026-09-29 [Pi 0.99.0 codemode: model-written JavaScript in a QuickJS sandbox](https://github.com/earendil-works/pi/blob/v0.99.0/packages/coding-agent/docs/cli.md#enable-codemode) |
| 2026-04-02 [deferred tool loading with search_tools](https://github.com/pardnchiu/Agenvoy/commit/650711b764) 2026-05-05 [MCP client adapter with stdio/HTTP transports](https://github.com/pardnchiu/Agenvoy/commit/bda9b15a08) 2026-06-07 [tool search registry](https://github.com/pardnchiu/Agenvoy/commit/679d5544c7) 2026-08-08 [MCP OAuth flow](https://github.com/pardnchiu/Agenvoy/commit/751e97c541) | Load tools on demand instead of declaring them all | 2026-09-29 [Pi 0.99.0 built-in MCP + tool_search](https://github.com/earendil-works/pi/blob/v0.99.0/packages/coding-agent/docs/mcp.md) |
| 2026-04-17 [invoke_subagent tool](https://github.com/pardnchiu/Agenvoy/commit/db690a51d4) 2026-04-25 [session action logs](https://github.com/pardnchiu/Agenvoy/commit/a78ae4e4c1) 2026-04-25 [session log streaming endpoint](https://github.com/pardnchiu/Agenvoy/commit/78806be270) 2026-04-28 [subagent name dispatch](https://github.com/pardnchiu/Agenvoy/commit/fb20bab34a) | Invoke sub-agents and track their logs | 2026-08-03 [Claude Code cross-session messaging](https://code.claude.com/docs/en/whats-new/2026-w32) |
| 2026-05-28 [extension tool loading](https://github.com/pardnchiu/Agenvoy/commit/d92d37512b) 2026-05-28 [extension install and upload skills](https://github.com/pardnchiu/Agenvoy/commit/4566ef49ae) 2026-05-29 [extension marketplace](https://github.com/pardnchiu/Agenvoy/commit/ddc9b02903) 2026-06-08 [ext_ extension tools and scaffolding](https://github.com/pardnchiu/Agenvoy/commit/2f2ba6e3ae) | Everything is a plugin | 2026-08-13 [DeepSeek Harness: everything is a plugin](https://www.deepseek.com/harness/en/) |
| 2026-06-03 [ask_user interruption async resume](https://github.com/pardnchiu/Agenvoy/commit/137b322243) 2026-06-04 [preserve in-progress action memory across interruptions](https://github.com/pardnchiu/Agenvoy/commit/a6b5e98f39) 2026-06-29 [pending task resume](https://github.com/pardnchiu/Agenvoy/commit/757493e53b) 2026-07-03 [write_todo checklist flow](https://github.com/pardnchiu/Agenvoy/commit/3b7c69cd05) | Resume long tasks from a checkpoint | 2026-08-04 [pi harness v2 in-memory storage](https://github.com/earendil-works/pi/commit/1d0c974713) 2026-08-05 [pi indexed harness recovery queries](https://github.com/earendil-works/pi/commit/591f22a618) 2026-08-05 [pi validate harness recovery record logs](https://github.com/earendil-works/pi/commit/a5953d2e15) 2026-08-05 [pi harness v2 jsonl backend](https://github.com/earendil-works/pi/commit/651d5d6a53) 2026-08-06 [pi atomic writes + torn-tail truncation](https://github.com/earendil-works/pi/commit/a838c069e6) 2026-08-14 [pi AgentHarness R3 generation recovery](https://github.com/earendil-works/pi/commit/a606448360) 2026-08-14 [pi AgentHarness R4 tool execution](https://github.com/earendil-works/pi/commit/d411d7b05e) |
## What you can do with it
Ask live questions and get live answers (Web Search / Tool Generate)
> What's the weather in Taipei?
>
> The agent finds current data, calls tools, and gives you the answer.
>
> If a tool doesn't exist, it builds one.
Turn one sentence into automation (Scheduler)
> Report TSMC stock price every morning at 8am
>
> The agent asks:
>
> - Where to push results
> - What format you want
> - When to run
>
> Then creates the schedule automatically.
Ask questions about your local files (File Search / RAG)
> Find all invoices from last year
>
> Which document mentions Prompt guide?
>
> The agent searches your local files and answers directly.
Finish multi-step work (Skills / Sub-agents)
> Summarize today's GitHub commits and generate a progress report
>
> The agent breaks down the task, calls tools, combines results, and replies.
Work with the agents you already use (MCP Server)
> Agenvoy is also an MCP server.
>
> Claude Code, Codex, OpenCode, and other AI agents can connect and:
>
> - Use all your sandboxed tools
> - Auto-build new tools when none exist
> - Share every tool across all agents
>
> One line of config. Instant shared tool library.
> Tools created in the demo: [`fetch_weather`](https://github.com/pardnchiu/Agenvoy/tree/master/doc/demo/fetch_weather) · [`fetch_crypto_price`](https://github.com/pardnchiu/Agenvoy/tree/master/doc/demo/fetch_crypto_price)
#### Claude Code creates a weather tool (1)
#### Codex reuses it and creates a crypto tool (2)
## Who it's for
If you want AI to complete work—not merely respond—within an environment you control, Agenvoy is for you:
- People who want to turn research, file work, and recurring reporting into reusable automation
- Developers who want a self-hosted agent with local data control and sandbox guardrails
- Teams that want Claude Code, Codex, and other agents to share tools instead of rebuilding them
- Technical operators who need private access to a local agent through the Web, Telegram, or Discord
---
## Drive Your Agent From the Browser
Manage sessions, tools, schedules, and memory from a browser. The dashboard ships inside the binary — start the daemon and open http://127.0.0.1:17989. It is served by your own machine, so nothing leaves your device.
Turn one sentence into automation (Scheduler)
> Report TSMC stock price every morning at 8am
>
> The agent asks:
>
> - Where to push results
> - What format you want
> - When to run
>
> Then creates the schedule automatically.
Ask questions about your local files (File Search / RAG)
> Find all invoices from last year
>
> Which document mentions Prompt guide?
>
> The agent searches your local files and answers directly.
Finish multi-step work (Skills / Sub-agents)
> Summarize today's GitHub commits and generate a progress report
>
> The agent breaks down the task, calls tools, combines results, and replies.
Work with the agents you already use (MCP Server)
> Agenvoy is also an MCP server.
>
> Claude Code, Codex, OpenCode, and other AI agents can connect and:
>
> - Use all your sandboxed tools
> - Auto-build new tools when none exist
> - Share every tool across all agents
>
> One line of config. Instant shared tool library.
> Tools created in the demo: [`fetch_weather`](https://github.com/pardnchiu/Agenvoy/tree/master/doc/demo/fetch_weather) · [`fetch_crypto_price`](https://github.com/pardnchiu/Agenvoy/tree/master/doc/demo/fetch_crypto_price)
| Claude Code creates a weather tool (1) | Codex reuses it and creates a crypto tool (2) | Agenvoy tests both tools (3) |
| :--- | :--- | :--- |
| [](https://youtu.be/on5IaoxBO1E) | [](https://youtu.be/2DDFCIcbnso) | [](https://youtu.be/KPs4o9xDFjM) |
---
Source: https://agenvoy.com/docs/introduction-designs.md
# Validated Designs
Design choices in Agenvoy that were later adopted or confirmed elsewhere, with their dates and commits. Tool, sub-agent, and resume designs are in [Tool & Task Designs](/docs/introduction-designs-tools).
Agenvoy has already implemented the design directions below, and other teams have since adopted similar approaches, confirming that these directions solve real problems agents face in practice. Every milestone links to a public commit or release, so you can check it yourself.
| Shipped in Agenvoy | Design | Same direction elsewhere |
| :--- | :--- | :--- |
| 2025-06-28 [Implement core application](https://github.com/agenvoy/cim-prototype/commit/df4fa95a01) 2025-06-29 [add traditional memory structure](https://github.com/agenvoy/cim-prototype/commit/d9a109c270) | Summary-based memory for unbounded context | 2025-11-24 [Claude client-side compaction](https://platform.claude.com/docs/en/release-notes/overview#november-24-2025) |
| 2026-01-04 [bubblewrap sandbox](https://github.com/pardnchiu/HakoRun/commit/87af1ba486) 2026-02-01 [harden bubblewrap sandbox](https://github.com/pardnchiu/HakoRun/commit/0cae0ed8c5) 2026-03-07 [block sensitive paths and credentials](https://github.com/pardnchiu/Agenvoy/commit/fdaaa62b3b) 2026-03-18 [sandbox execution with bubblewrap](https://github.com/pardnchiu/Agenvoy/commit/1f458d3596) | Sandbox mode for secure agents | 2026-03-16 [NVIDIA NemoClaw](https://blogs.nvidia.com.tw/blog/nvidia-announces-nemoclaw) |
| 2026-02-07 [Copilot device code login](https://github.com/pardnchiu/Agenvoy/commit/3d4020dfb2) 2026-04-04 [OpenAI Codex (OAuth) provider](https://github.com/pardnchiu/Agenvoy/commit/748b551ce1) | Sign in with an existing AI subscription | 2026-09-29 [Pi 0.99.0 Sign in with ChatGPT](https://github.com/earendil-works/pi/blob/v0.99.0/packages/coding-agent/docs/providers.md#authenticate-interactively) |
| 2026-02-27 [LLM-driven agent routing](https://github.com/pardnchiu/Agenvoy/commit/0514cc1127) 2026-03-10 [configurable dispatcher model selection](https://github.com/pardnchiu/Agenvoy/commit/e4274818e7) 2026-04-15 [strengthen agent tier routing](https://github.com/pardnchiu/Agenvoy/commit/055af67e44) 2026-06-10 [tiered routing across providers](https://github.com/pardnchiu/Agenvoy/commit/d3c5d9f63a) 2026-09-11 [configurable model tiers for agent and subagent routing](https://github.com/pardnchiu/Agenvoy/commit/2032737d23) 2026-09-11 [model tier controls on fallback priority](https://github.com/pardnchiu/Agenvoy/commit/b393d22f12) | Route each task to the model best suited to it | 2026-09-14 [Copilot auto model selection tiers: efficiency / balance / intelligence](https://github.blog/changelog/2026-09-14-configure-cost-and-quality-in-copilot-auto-model-selection/) 2026-09-29 [Pi 0.99.0 virtual models: per-request model and thinking level](https://github.com/earendil-works/pi/blob/v0.99.0/packages/coding-agent/docs/virtual-models.md) 2026-09-29 [Pi 0.99.0 classifier models](https://github.com/earendil-works/pi/blob/v0.99.0/packages/coding-agent/docs/llama-cpp.md#classification) |
---
Source: https://agenvoy.com/docs/introduction-designs-tools.md
# Tool & Task Designs
Design choices in how Agenvoy loads tools, runs sub-agents, and resumes long tasks that were later adopted or confirmed elsewhere, with their dates and commits. Memory, sandbox, sign-in, and routing designs are in [Validated Designs](/docs/introduction-designs).
| Shipped in Agenvoy | Design | Same direction elsewhere |
| :--- | :--- | :--- |
| 2026-03-23 [script tool support with sandboxed execution](https://github.com/pardnchiu/Agenvoy/commit/eb789ef4be) 2026-05-14 [script tool scaffolding skill](https://github.com/pardnchiu/Agenvoy/commit/33a62c9161) 2026-06-05 [script tool runtime metadata](https://github.com/pardnchiu/Agenvoy/commit/74ac5bf7eb) | Custom JS / Python tools run in the sandbox | 2026-09-29 [Pi 0.99.0 codemode: model-written JavaScript in a QuickJS sandbox](https://github.com/earendil-works/pi/blob/v0.99.0/packages/coding-agent/docs/cli.md#enable-codemode) |
| 2026-04-02 [deferred tool loading with search_tools](https://github.com/pardnchiu/Agenvoy/commit/650711b764) 2026-05-05 [MCP client adapter with stdio/HTTP transports](https://github.com/pardnchiu/Agenvoy/commit/bda9b15a08) 2026-06-07 [tool search registry](https://github.com/pardnchiu/Agenvoy/commit/679d5544c7) 2026-08-08 [MCP OAuth flow](https://github.com/pardnchiu/Agenvoy/commit/751e97c541) | Load tools on demand instead of declaring them all | 2026-09-29 [Pi 0.99.0 built-in MCP + tool_search](https://github.com/earendil-works/pi/blob/v0.99.0/packages/coding-agent/docs/mcp.md) |
| 2026-04-17 [invoke_subagent tool](https://github.com/pardnchiu/Agenvoy/commit/db690a51d4) 2026-04-25 [session action logs](https://github.com/pardnchiu/Agenvoy/commit/a78ae4e4c1) 2026-04-25 [session log streaming endpoint](https://github.com/pardnchiu/Agenvoy/commit/78806be270) 2026-04-28 [subagent name dispatch](https://github.com/pardnchiu/Agenvoy/commit/fb20bab34a) | Invoke sub-agents and track their logs | 2026-08-03 [Claude Code cross-session messaging](https://code.claude.com/docs/en/whats-new/2026-w32) |
| 2026-05-28 [extension tool loading](https://github.com/pardnchiu/Agenvoy/commit/d92d37512b) 2026-05-28 [extension install and upload skills](https://github.com/pardnchiu/Agenvoy/commit/4566ef49ae) 2026-05-29 [extension marketplace](https://github.com/pardnchiu/Agenvoy/commit/ddc9b02903) 2026-06-08 [ext_ extension tools and scaffolding](https://github.com/pardnchiu/Agenvoy/commit/2f2ba6e3ae) | Everything is a plugin | 2026-08-13 [DeepSeek Harness: everything is a plugin](https://www.deepseek.com/harness/en/) |
| 2026-06-03 [ask_user interruption async resume](https://github.com/pardnchiu/Agenvoy/commit/137b322243) 2026-06-04 [preserve in-progress action memory across interruptions](https://github.com/pardnchiu/Agenvoy/commit/a6b5e98f39) 2026-06-29 [pending task resume](https://github.com/pardnchiu/Agenvoy/commit/757493e53b) 2026-07-03 [write_todo checklist flow](https://github.com/pardnchiu/Agenvoy/commit/3b7c69cd05) | Resume long tasks from a checkpoint | 2026-08-04 [pi harness v2 in-memory storage](https://github.com/earendil-works/pi/commit/1d0c974713) 2026-08-05 [pi indexed harness recovery queries](https://github.com/earendil-works/pi/commit/591f22a618) 2026-08-05 [pi validate harness recovery record logs](https://github.com/earendil-works/pi/commit/a5953d2e15) 2026-08-05 [pi harness v2 jsonl backend](https://github.com/earendil-works/pi/commit/651d5d6a53) 2026-08-06 [pi atomic writes + torn-tail truncation](https://github.com/earendil-works/pi/commit/a838c069e6) 2026-08-14 [pi AgentHarness R3 generation recovery](https://github.com/earendil-works/pi/commit/a606448360) 2026-08-14 [pi AgentHarness R4 tool execution](https://github.com/earendil-works/pi/commit/d411d7b05e) |
---
Source: https://agenvoy.com/docs/introduction-capabilities.md
# Core Capabilities
What Agenvoy can do at a glance, plus the browser dashboard and the chatbot channels it answers on.
## Core capabilities
| Capability | Description |
| :--- | :--- |
| Auto tool generation | Builds and saves tools when they're missing |
| Self-scheduling | Create cron jobs with a single sentence |
| Long-term memory | Retains key info and context |
| File search | Answers from your local files |
| Sub-Agent | Multi-agent collaboration |
| MCP client | Connect to external MCP services via official go-sdk (live tool refresh) |
| MCP server | Expose sandboxed tools to any MCP-compatible agent |
| Reasoning guides | On-demand rules via `reasoning_guide(topics=[...])`, several topics in one call |
| Tool Market | Share and install tools |
| Image generation | Generate images through a configured provider |
| Live command output | Stream `run_command` progress to the TUI and Web dashboard |
| Secure file boundary | Confirm sensitive paths and out-of-home access before granting them |
| MCP OAuth | Log in to HTTP MCP servers and persist tokens in the OS keychain |
| Transcription | Audio and video to text |
| Self-improvement | Auto-fixes after execution failures |
Knowledge notes (operator notes) were removed in v1.0.23; keep reference material in files and use File search instead.
## Drive Your Agent From the Browser
Manage sessions, tools, schedules, and memory from a browser. The dashboard ships inside the binary — start the daemon and open http://127.0.0.1:17989. It is served by your own machine, so nothing leaves your device.
## Chatbot Integrations
Agenvoy currently supports **Telegram and Discord** as chatbot channels. The local daemon initiates outbound connections to these platforms, so you only need to configure a bot token—without exposing inbound ports, setting up a reverse proxy, or making your host public.
Since **v0.34.4**, Telegram and Discord have paused the default flow that automatically replies to voice input with voice output. You can still use STT/TTS tools to generate audio and send the resulting audio files to either channel.
---
Source: https://agenvoy.com/docs/introduction-install.md
# Install and Model Picks
The one-line installer and the model setup the developer recommends.
## One-line install
macOS / Linux distributions
Run this in a terminal:
```bash
curl -fsSL https://agenvoy.com/scripts/install.sh | bash
```
> **macOS tip:** If you run schedules on a MacBook, also run:
>
> ```bash
> sudo pmset -c sleep 0
> ```
>
> This prevents sleep from interrupting schedules.
Windows (via WSL)
First open PowerShell as an administrator, then list and install a Linux distribution:
```powershell
wsl --online --list
wsl --install
```
After installation, restart your computer, open a WSL terminal, and run:
```bash
curl -fsSL https://agenvoy.com/scripts/install.sh | bash
```
## Developer Recommendations
A cost-effective model setup to get started:
1. Choose a subscription model for everyday primary use, such as:
- GitHub Copilot ($10/mo) — pick `gpt-5.6-luna`: its quota drains slowly enough for daily use and it is capable enough for most work
- OpenAI ChatGPT Plus ($20/mo)
- SuperGrok ($30/mo)
2. **To try it for free**, no subscription needed:
- **[Ollama Cloud](https://ollama.com/settings/keys)** — create a free API key, then pick **Ollama Cloud** in `/model add` and add `gemma4:31b`. The free plan has a usage cap; check what is left at ollama.com, since `Shift+U` no longer lists Ollama Cloud.
---
Source: https://agenvoy.com/docs/quickstart.md
# Quickstart
The shortest path from nothing to a working agent: one install command, one free API key, one model. No credit card, no config file editing.
If you already have a provider account, [Getting Started](/docs/getting-started) covers the same ground with more detail and more options.
## 1. Install
```bash
curl -fsSL https://agenvoy.com/scripts/install.sh | bash
```
The script detects macOS or Linux, installs the `agen` binary to `/usr/local/bin`, and pulls in what it needs (`git`, `make`, a C compiler, `python3`, `node`, `pdftotext`, and `bwrap` on Linux). It asks for `sudo` once, to write into `/usr/local/bin`.
Check it landed:
```bash
command -v agen
```
## 2. Get a free Ollama Cloud key
Ollama Cloud is the easiest starting point: the free plan needs no billing setup, and `gemma4:31b` runs on it — enough to get a working agent without a card on file.
1. Open **[ollama.com/settings/keys](https://ollama.com/settings/keys)**
2. Sign up or sign in
3. Create a new API key
4. Copy the key
Keep the key on your clipboard for the next step.
## 3. Add the model
Start the TUI:
```bash
agen
```
Then, inside it:
| Step | What you do |
|---|---|
| 1 | Type `/model` and press **Enter** |
| 2 | Choose **add** — "add model from provider" |
| 3 | Press → to the **API Key** tab and choose **Ollama Cloud** (the picker opens on **OAuth**; `o` on a row opens that provider's key page) |
| 4 | Paste the key. Input is masked, and the key goes straight to the OS keychain — never into a config file, never into the conversation |
| 5 | Agenvoy fetches Ollama Cloud's live model list. Pick **`gemma4:31b`** |
That is the whole setup. The model is registered in `~/.config/agenvoy/config.json`; the credential is not.
> Pick it because it runs on the free plan, so the agent works with no card on file. The free plan has a usage cap; check it on your Ollama account, since **Shift+U** no longer reports Ollama Cloud (Removed in v1.2.0). Open-weight models call tools less reliably than frontier models — worth knowing before you blame the agent. Treat it as the dispatcher, and point the session model at a subscription plan (ChatGPT Plus, SuperGrok) once you have one.
## 4. Ask it something
Type a question into the input box and press **Enter**:
```
What's the weather in Taipei right now?
```
No weather tool ships with Agenvoy. Watch what happens instead: the agent searches its own registry, finds nothing, fetches the tool-building contract, picks a public API, writes a Python tool, sandbox-tests it, then calls it and answers. The tool is saved — the second time you ask, it just runs.
Try a few more:
```
Summarize the README in this folder
Report the TSMC stock price every morning at 8am
Which of my documents mentions the prompt guide?
```
Next: [After the First Run](/docs/quickstart-next) covers the six keys to know and where to read next.
---
Source: https://agenvoy.com/docs/quickstart-subscriptions.md
# Claude Code, Codex & Copilot Subscriptions
How to run Agenvoy on an existing subscription instead of an API key, after the quickstart works.
When the free tier stops being enough, the next step is not another pay-per-token key — it is a subscription you may already be paying for. These four are the first choice for a second, fall-back model: the cost is already fixed monthly, so an agent that takes a few extra turns doesn't move the bill.
| Provider | Billed against | Where it lives in the menu |
|---|---|---|
| Codex | Your ChatGPT / Codex subscription | **OAuth** tab → `OpenAI Codex` |
| Grok (xAI) | Your xAI subscription | **OAuth** tab → `Grok (xAI)` |
| GitHub Copilot | Your GitHub Copilot subscription | **OAuth** tab → `GitHub Copilot` |
| Claude Code | Your Claude subscription, through the local `claude` CLI | **OAuth** tab → `Claude Code` (shown only when enabled at startup) |
None of them takes an API key. Codex and Grok open a browser authorization page; Copilot uses a device-code flow. Either way the token lands in the OS keychain like any other credential. Claude Code has no login of its own: it runs the `claude` CLI you have already installed and signed in, so it must be switched on when Agenvoy starts.
## Add Codex
| Step | What you do |
|---|---|
| 1 | `/model` → **add** |
| 2 | The picker opens on the **OAuth** tab (←/→ switches tabs) |
| 3 | Choose **`OpenAI Codex`** — not `OpenAI` on the **API Key** tab, which is pay per token. Before v1.0.22 this was OpenAI → *OpenAI method* → `Codex subscription` |
| 4 | The OAuth popup opens and your browser goes to the authorization page once it is ready (**Enter** re-opens it). Sign in and approve — the flow has a 15-minute window |
| 5 | Back in the TUI, pick a model from the list your subscription exposes |
## Add Grok (xAI)
| Step | What you do |
|---|---|
| 1 | `/model` → **add** |
| 2 | Stay on the **OAuth** tab |
| 3 | Choose **`Grok (xAI)`** |
| 4 | Same browser OAuth flow as Codex |
| 5 | Pick a model |
## Add GitHub Copilot
| Step | What you do |
|---|---|
| 1 | `/model` → **add** |
| 2 | Stay on the **OAuth** tab |
| 3 | Choose **`GitHub Copilot`** |
| 4 | The popup shows a verification URL and a user code; press **Enter** to open that page in the browser, enter the code and approve. Same 15-minute window |
| 5 | Pick a model from the ones your Copilot plan offers |
## Add Claude Code
| Step | What you do |
|---|---|
| 1 | Install the `claude` CLI and sign in to it with your Claude account; it must be on `PATH` |
| 2 | `agen stop`, then start with `agen --enable-claude-code`. The flag is refused while the daemon is running. A TUI started later without the flag follows the daemon's setting |
| 3 | `/model` → **add**, stay on the **OAuth** tab and choose **`Claude Code`** — the row is hidden until step 2 is done and the binary is found |
| 4 | No browser step. Pick a model from the list the `claude` CLI reports for your subscription |
Details such as caching and session reuse are on [Claude Code Provider](/docs/providers-claude-code).
> Already authenticated once with Codex, Grok or Copilot? The flow asks ` token exists re-login?` first. **`No keep existing`** reuses the stored token and jumps straight to the model list — pick `Yes re-authenticate` only when switching accounts.
With more than one model registered, **Shift+W** / **Shift+S** cycles the session model from the TUI (`auto` lets the dispatcher pick), and `/model dispatch` / `/model summary` set the dispatcher and summary models explicitly.
---
Source: https://agenvoy.com/docs/quickstart-next.md
# After the First Run
The keys to know once Agenvoy is running, and where to read next.
## Know these six keys
With the input box empty:
| Key | Effect |
|---|---|
| `/` | Command palette — matches on command name or description |
| **Shift+Tab** | Toggle auto-approve (otherwise each non-read-only tool call asks first) |
| **Shift+F** | Toggle fast mode |
| **Shift+G** | Toggle `CLAUDE.md` / `AGENTS.md` injection (off at start). When on and the work directory has one of them, `[CLAUDE.md]` or `[AGENTS.md]` shows on the mode line and the file is added to every turn's context; `CLAUDE.md` wins when both exist |
| **Shift+W** / **Shift+S** | Cycle the session model (`auto` or a registered model) |
| **Esc** | Dismiss a popup, the `/` command picker or the notice box; during a run it asks whether to cancel or pause |
**Enter** sends. **Alt+Enter** inserts a newline. **Enter** during a run sends plain text as a steer message rather than interrupting; `/` commands wait until the run ends.
## Where to go next
| You want to | Read |
|---|---|
| Add a second provider, or switch models per session | [Managing Models](/docs/providers-config) |
| Understand what the agent can already do | [Built-in Tools](/docs/built-in-tools) |
| Make the agent build and keep its own tools | [Tool Extension](/docs/tool-extension) |
| Share your tools with Claude Code or Codex | [MCP Server](/docs/mcp-server) |
| Schedule something to run every morning | [Scheduler](/docs/scheduler-skills) |
| Drive it from a browser instead | Start `agen`, then open `http://127.0.0.1:17989` |
---
Source: https://agenvoy.com/docs/quickstart-troubleshooting.md
# If Something Goes Wrong
Fixes for the problems beginners hit most often during the quickstart.
| Symptom | Fix |
|---|---|
| `agen: command not found` | `/usr/local/bin` is not on your `PATH`. Open a new shell, or add it |
| The model list is empty after pasting the key | The key was rejected. Re-run `/model` → add → Ollama Cloud and paste it again — check for a trailing space |
| Every tool call stops and asks for confirmation | That is the default. **Shift+Tab** turns on auto-approve for the session |
| The agent answers but never calls a tool | Small open-weight models drop tool calls. Register a larger model — a subscription provider (`codex`, `grok-oauth`, `copilot`) needs no per-token billing |
| A command fails with a permission error | Commands run inside an OS sandbox. See [Sandbox](/docs/sandbox) for what it blocks and how to allow more |
---
Source: https://agenvoy.com/docs/getting-started.md
# Getting Started
## Prerequisites
- Linux (sandbox via bubblewrap; `bwrap` auto-installs through apt/dnf/yum/pacman/apk if missing) or macOS (`sandbox-exec`)
- At least one LLM provider account — an OAuth subscription (Codex, Copilot, Grok; Claude Code through the local `claude` CLI when started with `agen --enable-claude-code`) or an API key (OpenAI, Claude, Gemini, Grok, DeepSeek, Mistral, NVIDIA, Ollama Cloud, OpenRouter, Cloudflare), or a local OpenAI-compatible server (Ollama, LM Studio, custom URL)
- Go 1.25.1 or higher, only if building from source
- Optional: `pdftotext` (poppler-utils) so `read_files` can parse PDFs
- Optional: `OPENAI_API_KEY` to enable semantic search (`text-embedding-3-small`)
- Optional: an OpenAI, Gemini or OpenRouter credential to enable audio — `/model stt` lets `read_files` transcribe audio and video, `/model tts` registers `generate_audio`. Both are off until a model is picked
## Install
```bash
curl -fsSL https://agenvoy.com/scripts/install.sh | bash
agen
```
Or build from source:
```bash
git clone https://github.com/pardnchiu/Agenvoy.git
cd agenvoy
make build
```
`make build` compiles, stamps the current exact-match git tag into `internal/runtime.CurrentVersion`, installs the binary to `/usr/local/bin/agen`, and refreshes the bundled skills and tools.
## Configure at least one provider
Launch the TUI and run `/model` — it walks through provider selection, live model discovery from that provider's API, and credential storage. Tokens land in the OS keychain (`security` on macOS, `secret-tool` on Linux) under the fixed service name `agenvoy`; when `secret-tool` is unavailable they go to `~/.config/agenvoy/.secrets` (mode 0600). Reads try the keychain, then that file, then an environment variable of the same name.
The main config lives at `~/.config/agenvoy/config.json`; it holds registered models and runtime limits, never credentials.
## First run
```bash
agen
```
The daemon starts automatically if it isn't already running. Since v1.2.0 the daemon runs every request and the TUI talks to it over a Unix socket; while it is unreachable the status line shows `Connecting`. Inside the TUI, `/new` creates a session (optionally `/new `, then pick a role as its description) and `/session` moves between sessions.
The input box is always focused — type your message and press **Enter** to send (**Alt+Enter** inserts a newline; **Enter** during a run sends plain text as a steer message; `/` commands wait until the run ends). Type **`/`** to open the command palette (matches command name or description); **Tab**/**Enter** confirms the highlighted entry, **Esc** dismisses it. With the input empty: **Shift+Tab** toggles auto-approve, **Shift+F** toggles fast mode, **Shift+W**/**Shift+S** cycles the session model, **Shift+A**/**Shift+D** cycles the session reasoning level (`auto` included), and **Shift+G** toggles `CLAUDE.md` / `AGENTS.md` injection.
---
Source: https://agenvoy.com/docs/getting-started-browser.md
# Drive It From the Browser
Using the embedded web dashboard instead of the TUI, and where to read next.
## Or drive it from the browser
The daemon serves a dashboard from the same port the API runs on. With `agen` running, open:
```
http://127.0.0.1:17989
```
The page is embedded in the binary, so nothing is fetched from a remote host and nothing leaves your machine. Since v1.0.1 the third-party libraries it uses are downloaded once at daemon startup into `~/.config/agenvoy/vendor/` and served locally, and a service worker precaches the rest — so the dashboard keeps working with no network at all. Sessions, chat, schedules, skills, models, MCP servers and roles are all editable there (rules were renamed roles in v1.1.0; operator notes were removed in v1.0.23). Since v1.2.0 the sidebar lists **Schedule** and **Skills** next to **Roles**; the Skills page adds a **Skill Source** panel that turns whole skill sources on or off, and a schedule's test button reads `test in ` and opens that session's chat (`cli-` sessions included). Since v1.0.12 the **System** settings panel also holds the Telegram / Discord / admin channel setup (moved out of its own page), launch-on-login, reply language, **Official guide** (v1.2.0) and the output directory with an `open dir` shortcut. The **Other** group below it holds the **Update** card, which compares the running build against the latest GitHub release and can start `agen update` in a terminal window (moved here in v1.2.0), then the **Doc**, **License** and **Source** entries; the last two are the notices AGPL-3.0 §7(b) requires the interface to keep (v1.0.16) — see [License](/docs/license).
Since v1.2.2 replies and opened Markdown files render charts, diagrams and formulas — see [Rendered with NanoMD](/docs/getting-started-browser-nanomd).
To run one request without the TUI, post it:
```bash
curl -sS -H 'Content-Type: application/json' \
-d '{"content":"summarize the latest changes in main.go"}' \
http://127.0.0.1:17989/v1/send
```
Without `session_id` the request runs in a new temporary session; `model` and `skill` are optional. There is no `allow_all` field.
## Next steps
- Sessions & Agents — sessions, agent routing, and subagents
- Execution Engine — the iteration loop and three-pass tool dispatch
- Providers — supported LLM backends and the dispatcher model
- MCP Client — plug in external tool servers
- CLI Commands — full command list
---
Source: https://agenvoy.com/docs/getting-started-browser-nanomd.md
# Rendered with NanoMD
Since v1.2.2 the browser dashboard renders agent replies and Markdown files with [NanoMD](https://nanomd.pardn.io), so charts, diagrams and formulas show up as figures instead of source.
## What renders
| Markdown | Result |
| --- | --- |
| ```` ```chartjs ```` with `{"type": ..., "data": {...}}` | Chart: bar, line, pie, doughnut, radar, polarArea, scatter, bubble |
| ```` ```mermaid ```` | Diagram: flowchart, sequenceDiagram, stateDiagram-v2, classDiagram, erDiagram, gantt, timeline |
| ```` ```math ```` or inline `$...$` | KaTeX formula |
| Any other fenced code | Syntax highlighting with a copy button |
| GFM tables, task lists, footnotes, `==highlight==`, `> [!NOTE]` callouts | Styled Markdown |
A block that fails to parse (invalid JSON, unknown chart type, broken Mermaid syntax) shows its source instead. This page is rendered by NanoMD as well:
```chartjs
{"type": "bar", "data": {"labels": ["Q1", "Q2", "Q3", "Q4"], "datasets": [{"label": "Requests (k)", "data": [12, 19, 15, 24]}]}}
```
```mermaid
sequenceDiagram
Browser->>Daemon: POST /v1/send
Daemon->>Model: prompt + tools
Model-->>Daemon: Markdown reply
Daemon-->>Browser: SSE stream
```
```math
A = P\left(1 + \frac{r}{n}\right)^{nt}
```
## Where it renders
| Surface | Behavior |
| --- | --- |
| Web chat replies and reasoning (`chat-` sessions) | Rendered while streaming; figures in older messages render as they scroll into view |
| `.md`, `.markdown`, `.txt` file links in a reply | Open in the side panel, rendered with NanoMD; local images resolve through `/v1/file/open` |
| TUI, Telegram, Discord | Show the Markdown source |
## How the agent uses it
The system prompt tells the agent where figures render. In `chat-` sessions it adds them to both chat replies and `.md` files; in every other session it puts them only in `.md` files, because those chat surfaces would show the raw block. When content holds numbers across three or more items or time points, a process or a formula, the agent adds the matching chart, diagram or formula next to the table or text it summarizes, without being asked. It skips the figure when it would only restate a single number or a two-item comparison.
## About NanoMD
NanoMD is a TypeScript Markdown parser that turns Markdown into sanitized HTML and a JSON AST, loading Mermaid, KaTeX and Chart.js only when a page uses them. Agenvoy pins its parser and stylesheet in `page/vendor.json` (2.0.10 in v1.2.2); the daemon downloads them once into `~/.config/agenvoy/vendor/nanomd/` and serves them locally, so rendering works offline. Syntax reference and playground: [nanomd.pardn.io](https://nanomd.pardn.io).
See also: [Browser Dashboard](/docs/getting-started-browser).
---
Source: https://agenvoy.com/docs/sessions.md
# Sessions & Agents
## Session
A session is the core unit in Agenvoy. Each session has its own conversation context, memory, agent persona, and tool configuration.
Storage path: `~/.config/agenvoy/sessions//`
| File | Purpose |
|---|---|
| `history.json` | Recent conversation, delta-appended |
| `summary.json` / `.summary_cursor` | Rolling summary and its incremental cursor |
| `action.log` | Tool call audit log. One line per event, `[timestamp][window_hash][kind][task_hash] body` (`window_hash` is the writing window's hash, the same value events carry as `window_hash`); the trailing `task_hash` bracket was added in v1.0.2 so every line can be traced back to the run that wrote it, and the replay parser treats it as optional so pre-v1.0.2 lines still load |
| `.cmd_history` | TUI input history for this session |
| `.claude_code/_.json` | Since v1.1.3, one per `claude-code` model and system prompt hash used in the session (`_plain` appended for calls without tools): its Claude Code session id, message fingerprints and last answer, so a restarted daemon can `--resume` instead of replaying history (`@`, `/` and `\|` become `_`). v1.1.1 to v1.1.2 wrote one `claude_code_.json` per model in the session directory |
| `pending/` | Pending `ask_user` / confirm metadata, one JSON per task hash |
Everything else moved into `~/.config/agenvoy/.store/history.db`:
| SQLite table | Content |
|---|---|
| `session` | Display name, `self_id`, persona (`role`), model, reasoning level. Since v1.1.0 the persona is written to both the `role` column and the legacy `rule` column, and read from `role` first |
| `note` + `note_fts5` | Removed in v1.0.23 along with operator notes; tables left in an older database are no longer read |
| `messages` + `messages_fts5` | Full message archive and its full-text index |
| `action_history` | Finished runs and their tool results |
| `file_history` | Recorded versions of every file the tools changed. Since v1.0.14 the unique index is `(dir, name, task_id, session_id)` for rows that carry a task, so one task records a given file once — the first version it saw — instead of one row per save; the migration deletes the duplicates already stored. Rows with an empty `task_id` are unaffected |
| `usage` | Per-model token spend, 28-day retention. Carries `elapsed_ms` per send since v1.0.11, which the summaries turn into `output_tps`. Since v1.0.26 each row also records the ids of the tool calls in that reply (`tool_calls`); since v1.1.0 `input` for `claude` and `claude-code` includes cache writes, matching how other providers report uncached input |
Vectors, tool cache, error memory, and running-task presence live in ToriiDB (`.store/db_0` ... `db_3`). Up to v1.1.1 the daemon migrated legacy per-session `bot.json`, `bot.md`, `config.json`, `status.json`, `usage.log`, `summary.meta.json` and the `history/` task directory into SQLite at startup; v1.1.2 removed those migrations, so such files left by an older install are no longer read — upgrade through v1.1.1 first to carry them over. The `state` table was dropped in v0.35.3: a running task is now an `action::` key in ToriiDB, refreshed every 55 s with a 60 s TTL, so a killed process stops being online on its own rather than needing a PID liveness check.
### Session prefixes and lifetime
| Prefix | Lifetime |
|---|---|
| `cli-*` | Permanent — local CLI / TUI work (TUI `/new`, or `POST /v1/session`) |
| `chat-*` | Permanent — web / API work (`POST /v1/send` with `persist=true`) |
| `dc-*` | Permanent (Discord channels) |
| `tg-*` | Permanent (Telegram chats — per-chat, shared across users in that chat) |
| `temp-*` | Reaped after 30 min idle (default for `POST /v1/send` and subagent sessions) |
Cleanup runs every 30 minutes via cron (and once on startup) against the `temp-*` prefix only — `cli-*`, `chat-*`, `dc-*`, and `tg-*` are never auto-reaped.
The prefix also decides where a confirmation goes back to (since v1.2.0 a run started from the TUI asks only the TUI window that started it), and it is what the TUI session picker groups by: with at least two groups present the picker shows an `all` tab plus one tab per prefix, current session first. A daemon-side `fsnotify` watcher logs the ID and configured name of every newly created session directory.
---
Source: https://agenvoy.com/docs/sessions-persona.md
# Agent Persona
How a session's persona is defined, rendered into the system prompt, and stored.
## Agent persona
Each session carries its own persona, stored in the `session` row rather than a file:
| Field | Notes |
|---|---|
| `name` | Display name. Since v1.0.11 it is no longer backfilled with the session ID when left blank — a blank, session-ID-shaped, `tg-` or `dc-` name counts as auto-named and is replaced the first time a title is generated |
| `self_id` | Normalized to lowercase, up to 32 ASCII letters, digits, `_` or `-`; non-empty values must be unique across sessions (a duplicate returns 409); the lookup key for the `subagents` `self_id` parameter |
| `role` | Persona body, rendered into the system prompt's `## Bot Persona` block on each turn. Since v1.0.11 the operating identity named in that block is `self_id`, not `name`. Renamed from `rule` in v1.1.0: `GET` returns both `role` and `rule`, and `POST` still accepts `rule` (it wins when both are sent) |
| `model` / `reasoning` | Session model and reasoning level; `model: ""` resets to `auto`. Since v1.1.0 `reasoning` also accepts `auto`, which takes the level from the dispatcher's work-kind pick each turn and replaces the removed global `auto_reasoning` switch |
Edit `name`, `self_id` and the persona from the TUI with `/session` (`/session name|id|role`; `/bot` was folded into it in v1.0.22), or through `GET` / `POST` `/v1/session/:id` — one endpoint for the whole session object, with `POST` as a partial update.
## Where the persona is stored
Session configuration lives in the `session` table of `~/.config/agenvoy/.store/history.db`, not in per-session files: `name`, `self_id`, `model` (`auto` by default), `reasoning` (a level, or `auto` since v1.1.0), `role` (the persona body; renamed from `rule` in v1.1.0 — the old `rule` column is kept, written alongside and read as a fallback), plus the chat binding (`chat_id`, `guild_id`, `channel_id`, `user_id`). `self_id` is normalized to lowercase and accepts up to 32 ASCII letters, digits, `_`, or `-`; non-empty values must be unique.
The persona body is rendered into the system prompt's `## Bot Persona` block on every turn, which names `self_id` as the operating identity since v1.0.11. Edit it from the TUI with `/session role` (formerly `/bot`), or through `GET` / `POST` `/v1/session/:id`.
Up to v1.1.1 the daemon migrated legacy per-session `bot.json`, `bot.md`, `config.json`, `status.json`, `usage.log`, `summary.meta.json` and the `history/` task directory into SQLite at startup; v1.1.2 removed those migrations, so such files left by an older install are no longer read — upgrade through v1.1.1 first to carry them over. The `state` table itself was dropped in v0.35.3 — a session's live state is now a ToriiDB online marker, not a stored row.
---
Source: https://agenvoy.com/docs/sessions-routing.md
# Agent Routing
The ways a request reaches an agent: automatic dispatch, the subagents tool, and external agents.
Two ways decide which agent handles a task:
**1. Automatic** — A dispatcher LLM analyzes the input and picks the best-fit provider via `exec/model.Route` (`ResolveAgent()` before v1.2.1). Since v1.1.0 `pass`-tier models are never picked or used as fallbacks; a session pinned to a model still uses it.
Startup order: `exec.Prepare` (TUI-only exclusions, `/skill-name` match) → `exec.Run` (named skill resolution, agent resolution, session load; `exec.Start` was renamed `exec.Run` in v1.2.1) → `Execute`. Subagents and schedules call `exec.Run` directly with `isSub=true` and skip `exec.Prepare`. A named skill that is excluded or not found fails the run.
The `:self-id` one-shot override prefix was removed; delegate to a named session with `subagents(self_id=...)`, or switch sessions with `/session`.
**2. `subagents` tool** — An agent calls another agent in-process (no HTTP) during execution, inheriting `AllowAll` and `WorkDir` from the parent ctx. Since v1.1.0 `mode=invoke` requires `model` — the dispatcher fallback for a blank model was dropped; pass `model: "auto"` (hotfix after v1.1.0) to let the dispatcher pick from the task — and the tier list the parent picks from in `reasoning_guide(topics=[subagent_dispatch])` starts one tier lower than for its own work (S → A, A → B, B → C). An explicitly named model overrides the model a `self_id` session is pinned to; `model: "auto"` reuses that pinned model and only lets the dispatcher pick when there is none. `reasoning` defaults to `auto`, and an invalid level returns an error listing the valid ones.
Subagents run under a **collection-only charter** — they gather and report, they don't nest or write. The exclusion set is built from three parts:
| Source | Excluded |
|---|---|
| Charter base | `subagents`, `edit_file`, `edit_tool`, `test_tool`, `edit_skill`, `write_result`, `html_template`, `generate*` (wildcard), `open_file`, `schedules`, `store_secret`, `pkg_manage` (since v1.2.1; before that only `subagents`, `edit_file`, `generate*`) |
| TUI-only tools / skills | Whatever `configs/jsons/tui_tools.json` lists, e.g. `extension-upload`, `extension-install` |
| Caller's `exclude_tools` | Anything the parent passes in |
`ask_user` is **not** excluded — subagents can ask the user through the shared pending registry. A subagent's pending question is also published to the owning (parent) session, and the leg waits for that resume and returns the answered result. At most three subagent legs execute concurrently (`MAX_CONCURRENT_SUBAGENTS`); a fourth waits for a slot while its own `MAX_SUBAGENT_TIMEOUT_MIN` (30 min) timeout is already running, so wide fan-outs should be dispatched in batches of three. Each result carries a usage header that rolls up into the parent session's totals; since v1.0.22 the report itself is saved to `~/.config/agenvoy/download/temp-.md` and only its path comes back.
---
Source: https://agenvoy.com/docs/sessions-tasks.md
# Running Tasks
How tasks in one session run side by side, how to cancel them, and how interrupted work resumes.
## Per-session concurrency
`MAX_SESSION_TASKS` (`3` since v1.1.2; `NumCPU × 4` before) limits how many concurrent `Execute()` calls a single session can run. A task publishes its ToriiDB online marker and registers its cancel function *before* competing for a slot, so a queued task stays visible and cancellable instead of blocking invisibly. The marker carries a 60 s TTL refreshed every 55 s, so a killed or crashed process stops counting as online by expiry rather than by a PID check. The value is a package constant — it is not read from `config.json`.
## Cancelling work
Cancellation is **per task**, not per session: `POST /v1/session/:id/cancel/:task_hash` stops one running task (`{"cancelled": true}`); when that task hash is not running in this process it records a canceled event and returns `{"cancelled": false, "stale": true}`. The former `once_id` identifier was replaced by the task hash. Tool confirmations are answered separately with `POST /v1/session/:id/confirm/:confirm_hash`, where `confirm_hash` comes from the confirm event. There is deliberately no cancel-everything variant, so one long-running job can be dropped without disturbing the session's other tasks. In the TUI, `Esc` during a run (after any open `/` command picker and notice box are closed) opens the cancel-confirmation popup for the active task, or cancels it at once when nothing has been shown yet; since v1.2.0 the TUI sends the cancel to the daemon as a `cancel` frame on its socket.
## Resuming interrupted work
A run that stopped on an `ask_user` or a confirmation leaves a pending task behind. `GET /v1/session/:id/task` lists the ones that are actually resumable: a live run refreshes `action::` in ToriiDB every 55 s with a 60 s TTL, so a task abandoned by a closed window or a killed process reappears in that list within a minute, while a task still being worked on does not. Answer it with `POST .../resume`, or drop it with `DELETE .../:task_hash`. In the TUI this is `/pending`, which since v1.2.0 resumes through the daemon with a `pending` frame and asks any saved question inline.
Completed runs stay queryable too: `GET /v1/session/:id/task/history` lists them as `task_hash`, `end_at`, `objective`, `model`, and `reasoning` (optional `keyword` filter), and `GET .../:task_hash/history` returns one run's full action record as `content`.
---
Source: https://agenvoy.com/docs/execution-engine.md
# Execution Engine
Every session-backed entry point (TUI, which since v1.2.0 sends its runs to the daemon over the Unix socket instead of executing them itself, `/v1/send`, pending resume, Telegram, Discord) goes through the same startup: `exec.Prepare` matches a skill from the input, then `exec.Run` resolves a skill named by the caller, emits the skill and model selection events, picks the model and its fallbacks through `exec/model.Route`, and hands off to `exec.Execute()`. Subagents and the scheduler call `exec.Run` directly with `isSub=true` and skip `exec.Prepare`; the stateless `/v1/chat/completions` skips both and calls `exec.Execute()` itself. `exec.Start` was renamed `exec.Run` in v1.2.1, and `ResolveAgent` became `exec/model.Route` in the same release. Since v1.1.0 reasoning is a per-session setting: when it is `auto` (or unset) the selector inside `exec/model.Route` also decides the reasoning level from the kind of work, and the global `auto_reasoning` switch was removed. Auto routing never selects a `pass`-tier model, even when the message names it, and such models are left out of the fallback list; only a session pinned to one, or a caller passing it as the explicit `model`, runs it.
`exec.Execute()` runs the main loop for up to **256 iterations** (`limits.max_tool_iterations`; 128 before v1.1.2). Each iteration:
1. Assemble messages: `SystemPrompts` + `ContextHistories` (guardrail refusals dropped) + the summary message, if any + the turn context, if any + `UserMessage` + `ToolHistories` (`ContextHistories` and `UserMessage` were `OldHistories` and `UserInput` before v1.2.1) — appended in that order, never rewritten mid-run, so the prompt-cache prefix stays stable. Since v1.2.0 the turn context is one `user` message built each turn by `assignTurnContext`: the work directory and permission mode (not for stateless `/v1/chat/completions` runs), the names of the tools to call through `run_tool`, the skill list (omitted when `run_skill` is excluded), and the `CLAUDE.md` / `AGENTS.md` guide when the guide toggle is on; these used to sit in the system prompt, where every change reset the cache
2. Call `Agent.Send()` on the chosen provider
3. Parse `tool_calls` from the response
4. Dispatch the tool calls through `toolCall.go` (see [Tool Dispatch](/docs/execution-engine-tools))
5. Append results to `ToolHistories`; images returned by tools follow the round's results there as one `user` message (since v1.1.1 — they used to be injected into `UserInput`, which rewrote the cached prefix)
6. Stop when no `tool_calls` remain or the iteration limit is hit
Each `Send` writes one row to the SQLite `usage` table (model, input / output / cache-write / cache-hit tokens, elapsed time); since v1.0.26 the row also records the IDs of the tool calls that response requested. Since v1.1.0 the completion event no longer carries the remaining provider quota; `GET /v1/providers/quota` serves it on request.
There is **no inter-round delay** — rate-limit protection comes from provider round-trip latency, per-model cooldown, and per-tool timeouts.
## Cross-turn workdir reset
The workdir is supplied per request, not carried over from history: the TUI passes its own working directory, `/v1/send` resolves the `work_dir` field, and subagents inherit or resolve theirs. Since v1.2.0 it is written into the turn context on every run, not into the system prompt, so the cached system prefix survives a directory change; a `cd` in an earlier turn does **not** carry over through history.
- **Turn context** — a `Work directory: ...` line stating that this directory is authoritative for this turn, that earlier ones in history are ignored, and that `run_command` already starts there. Stateless `/v1/chat/completions` runs keep the line in their own system prompt instead
- **TUI** — when the agent runs `cd` inside `run_command`, the daemon sends a `workdir` frame to the TUI connection that owns the run and the TUI adopts that directory, so its next request carries it
The per-message metadata header (timestamp + working directory wrapped around each user message) and its TUI stripper `stripUserMetaHeader` were removed; the turn-context work directory line is the only workdir anchor.
---
Source: https://agenvoy.com/docs/execution-engine-tools.md
# Tool Dispatch
How one round of tool calls is checked, run, and recorded: the three passes, deferred schemas, which tools run concurrently, and how repeated calls are deduplicated.
## Three-pass tool concurrency
`toolCall.go` splits each round's tool calls into three serial passes; only Pass 2 fans out:
| Pass | Mode | Work |
|---|---|---|
| 1 — pre-flight | Serial | Unwrap `run_tool` into the named tool; refuse a tool called directly that is not in the loaded set (with a hint to fetch its schema with `find_tools` and call `run_tool`); duplicate-call dedupe (`tool\|args` hash); return the schema, and ask for a re-invoke, when a tool is called before its schema was fetched; existence check, so a name with no registered handler never runs (since v1.2.0); confirm gate; JSON-schema validation |
| 2 — execute | Concurrent for `IsConcurrent`-tagged tools, at most 5 at a time (`MAX_CONCURRENT_TOOLS`, v1.0.20) with the rest queued; serial otherwise | `tools.Execute` |
| 3 — commit | Serial | Land `sessionData.Tools` and `ToolHistories`, update the dedupe map, write the result cache, emit `EventToolResult` |
Since v1.2.0 tool schemas are deferred. For every provider except `claude-code`, the request's tools array holds only `find_tools`, `run_tool` and `reasoning_guide` (plus client tools; `reasoning_guide` joined in v1.2.1); every other tool is listed by name in the turn context, its schema is fetched with `find_tools`, and it is called as `run_tool(name, args)`. `claude-code` gets every tool except `run_tool`, but all of them except `find_tools` and client tools start unmarked, so the first call to each returns its schema instead of running. The per-tool `AlwaysLoad` flag was removed; use `find_tools` to load a schema. Removed in v1.2.0.
Concurrent-tagged built-in tools (18): `read_files`, `find_files`, `find_tools`, `file_history`, `chat_history`, `error_history`, `fetch_page`, `search_web`, `http_request`, `download_file`, `test_tool`, `calculate`, `subagents`, `list_chatbot`, `send_to_chatbot`, `reasoning_guide`, `html_template`, `write_todo` (since v1.0.26; `find_note` left the set when it was removed in v1.0.23). `generate_audio` and `generate_image` do not carry the flag. `api_*` / `script_*` / `ext_*` tools run concurrently only when their own definition declares it. `edit_file`, `run_command`, interactive tools, and MCP tools always run serially — the parallel `run_command_readonly` tool added in v1.0.23 was removed in v1.0.25.
Dedupe is per run, not per session: a repeated `tool|args` pair returns the earlier result instead of executing again, and `edit_file` invalidates the `read_files` entries covering the paths it touched. A separate 30-minute result cache in ToriiDB (`db_0`) covers `fetch_page`, `search_web`, and `http_request` `GET` calls only.
---
Source: https://agenvoy.com/docs/execution-engine-pending.md
# Pending Tasks and Cancellation
How a run waiting on the user is parked, and what cancel and pause do to a running task.
## Pending registry
`internal/runtime/pending.go` is the prefix-routed confirm/ask registry shared by the main agent and any in-process subagents. Producers (`toolCall` confirm, `ask_user` handler, `store_secret` handler) call `Ask(ctx, req)` and block on a per-entry buffered=1 reply channel. Each front-end registers a listener via `RegisterListener(prefix)` — the web dashboard `"chat-"`, and the Telegram / Discord listeners `"tg-"` / `"dc-"` (an empty prefix matches everything) — and claims only matching entries through `PickNextMatch(prefix, accept)`. Since v1.2.0 the TUI has no listener of its own: each daemon socket connection registers `"cli-"` with `RegisterListenerMatch(prefix, match)`, matching only requests whose ctx carries a window hash that connection started, and forwards them as `ask` frames, so a confirmation or question from a TUI run reaches only the TUI window that started it. A restricted-path confirmation asks for the system password in a TUI password field (skipped while sudo credentials are still cached); the daemon verifies it with `sudo -S -v` and reports the result in a `verify` frame when a password was entered or verification fails. Closing the connection resolves its open questions with an error. ctx cancellation removes the entry so a stale producer never wastes a human interaction.
The gate `HasListener(origin)` checks whether a listener whose prefix matches the session's origin (`OriginOf` recognizes `cli-`, `chat-`, `tg-`, `dc-`) is registered. With a match function, the listener counts only when the request's ctx passes it, so a TUI run whose window is disconnected finds no listener. This replaces the old global `pending.Active atomic.Bool`, so TUI, web, Telegram, and Discord confirm flows run side by side without blocking each other.
Since v1.0.23 `ask_user` splits by origin. From a TUI run (origin `cli-`) the question goes through `Ask` as an inline request and the same run continues with the answer. From any other origin the round's other tool results are committed, a pending snapshot is written (each tool result clamped to 32 KiB and its arguments to 4 KiB since v1.1.2 — 4 KiB / 1 KiB before; a clamped value carries a `...[N bytes elided]...` marker, and the resume prompt tells the model to re-run the call when the elided part matters), the run ends, and the answer resumes it as a new turn.
## Cancel and pause
Cancellation carries a cause since v1.0.10 (`context.WithCancelCause`). Only a cancel that names `runtime.ErrUserCanceled` — Ctrl+C in the TUI, or **Yes** in the Esc popup — deletes the pending task. Any other cancel, including the popup's **Pause**, tears down the run but leaves the pending record in place, so `/pending` or `GET /v1/session/:id/task` can resume it. Since v1.2.0 the TUI's `/pending` resumes through the daemon with a `pending` frame; a saved question is asked inline over the socket and the resumed run streams back to the same window. A TUI run is bound to its socket connection: closing the TUI cancels the run without `runtime.ErrUserCanceled`, so its pending task is kept.
The TUI's Ctrl+C and popup choices travel as a `cancel` frame (with `pause` for **Pause**), which the daemon applies through `exec.Cancel(taskHash, cause)` only for tasks that connection started. Since v1.2.0 `exec.Run` (named `exec.Start` before v1.2.1) generates the task hash and registers the cancel function before it emits the first event, so a cancel works from the first event onward; a cancel requested before the TUI has seen any task hash is held and sent once the first one arrives. Outside a popup, `Esc` first closes an open `/` command picker, then the notice box, then cancels the run (immediately if nothing has been shown yet, otherwise through the popup), then clears the input. An `ask_user` reply that fails for a reason other than user cancellation no longer discards the pending task either.
---
Source: https://agenvoy.com/docs/execution-engine-failures.md
# Send Failures and Fallback
What happens when a model call times out, is rate limited, runs out of quota or context, and how fallback picks the next model.
## Send failure handling
`Agent.Send()` failures escalate instead of retrying blindly:
| Failure | Behavior |
|---|---|
| Timeout | Up to `MAX_RETRY_TIMES` (3) retries on the same model — four attempts in all since v1.1.2, which was three — spaced by `SEND_TIMEOUT_RETRY_INTERVAL` (15 s) |
| Rate limit (HTTP 429 or a rate-limit message) | Register a 30-minute cooldown, retry the same model after 5 s, 10 s, then 15 s; then switch to the next fallback |
| Quota exhausted (HTTP 402 / 403 or a quota / billing message) | Register a 30-minute cooldown and switch to the next fallback immediately |
| Context-length exceeded | Since v1.1.1, compact history first (the same `ExtractContextHistories` → `ToolHistory` pass an exceeded token budget triggers) and resend; when that cannot compact, trim the oldest exchange (`compact.TrimFallback`) and resend; abort only when nothing is left to trim |
| No response while streaming | Health-probe every 30 s (`UNRESPONSIVE_PROBE_INTERVAL`), retry a failed probe every 10 s (`HEALTH_CHECK_TIMEOUT`), switch model after 3 failures (`MAX_RETRY_TIMES`) |
| Any other error | Switch to the next fallback model; abort when no healthy fallback remains |
Fallback candidates come from the priority-ordered list `exec/model.Route` builds (`ResolveAgent` before v1.2.1), which since v1.1.0 excludes `pass`-tier models. `exec/model.Next` (`nextAgent` before v1.2.1) skips models from the failed model's provider (skipping models whose context window cannot hold the input was removed in v1.2.1), health-checks each candidate (`HEALTH_CHECK_TIMEOUT`, 10 s), and rebuilds the list until `MAX_RETRY_TIMES` (3) retry rounds have run — four passes in all since v1.1.2, which was three. A session bound to a specific model (anything other than `auto`) never falls back.
Switching models clears `ToolHistories` (or applies `compact.RawToolFallback`), resets the dedupe map, and restarts the counters — a fresh model never inherits the failed model's partial state.
## Timeout layers
| Layer | Value | Catches |
|---|---|---|
| Provider HTTP client | set inside `go-llm-router` per provider | Transport-level stalls |
| `AGENT_SEND_TIMEOUT_SEC` | `limits.agent_send_timeout_seconds` in `config.json`, default `600`; since v1.1.2 a smaller value is ignored | Exec-layer ceiling via `context.WithTimeout` |
| Unresponsive watchdog | probe every 30 s; after a failed probe, retry every 10 s; 3 failures switch model | A stream that hangs without erroring |
| Health check | 10 s | Liveness probe of each fallback candidate |
---
Source: https://agenvoy.com/docs/providers.md
# Providers
Provider implementations live in **[`go-llm-router`](https://github.com/pardnchiu/go-llm-router)** (v0.9.4), an external module extracted from Agenvoy. Agenvoy builds every agent through `router.New(cfg)` and calls one `Agent.Send()` surface — no per-vendor code remains in this repository.
## Supported list
Eleven vendors. Since v1.0.22 the TUI `/model add` picker has three tabs (←/→ switches), and each auth route is its own row: **OAuth** — Claude Code (`claude-code`, since v1.1.0), OpenAI Codex (`codex`), Grok (xAI) (`grok-oauth`), GitHub Copilot (`copilot`); **API Key** — OpenAI, Claude, Gemini, Grok, DeepSeek, Mistral, NVIDIA NIM, Ollama Cloud, OpenRouter; **Custom** — Cloudflare, Local/Custom (`compat`), plus any local Ollama / llama.cpp endpoint detected on its usual port. That is fifteen fixed rows (fourteen in v1.0.22). Before v1.0.22 it was one twelve-row list, where picking OpenAI or Grok opened a second *method* picker for API key vs. subscription. `o` on a row opens that provider's console (API-key page, or the plan page for subscriptions). `GET /v1/providers` flattens the same set into fourteen entries, giving `claude-code`, `codex` and `grok-oauth` their own IDs so an API client can address one auth route directly; `compat` is returned alongside them as a fifteenth row. Each row carries `logged_in`, which is meaningful only for the four subscription routes (`claude-code`, `codex`, `grok-oauth`, `copilot`), `console`, the console pages `GET /v1/provider/:provider/console` can redirect to, and `hidden`, set on `claude-code` while it is not enabled.
| Provider | ID | Auth | Notes |
|---|---|---|---|
| OpenAI | `openai` · `codex` | API key **or** OAuth | API key uses Chat Completions / Responses. The OAuth route runs on your ChatGPT / Codex subscription with no API key, over SSE |
| Anthropic Claude | `claude` · `claude-code` | API key **or** OAuth (Claude Code) | API key uses the Messages API, parallel tool use on by default. `claude-code` runs on your Claude subscription through the local `claude` CLI — see [Claude Code](#claude-code) |
| Google Gemini | `gemini` | API key | gemini-2.x / 3.x families |
| xAI Grok | `grok` · `grok-oauth` | API key **or** OAuth | API key is pay-per-token; the OAuth route runs on your xAI subscription |
| GitHub Copilot | `copilot` | OAuth | Device-code login flow, on your GitHub subscription. Since v1.0.25 its context window comes from the Copilot models API (`max_context_window_tokens`, cached 24 hours), not the general 128K default |
| DeepSeek | `deepseek` | API key | Flash and Pro models (e.g. `deepseek-v4-pro`), listed live from `api.deepseek.com/v1/models`; `deepseek-chat` / `deepseek-reasoner` are gone |
| Mistral | `mistral` | API key | Mistral's own hosted models |
| NVIDIA NIM | `nvidia` | API key | Nemotron, Llama, Mistral, and other hosted open-weight models; free tier needs no billing setup |
| Ollama Cloud | `ollama-cloud` | API key | Ollama's hosted models. Quota display was removed; check usage on ollama.com (Removed in v1.2.0) |
| OpenRouter | `openrouter` | API key | Aggregator — routes to models from many vendors through one key |
| Cloudflare | `cloudflare` | API token + account ID | Workers AI; optional gateway ID |
---
Source: https://agenvoy.com/docs/providers-claude-code.md
# Claude Code Provider
Running models through the locally installed `claude` CLI on your Claude subscription.
`claude-code` (since v1.1.0) uses the locally installed and logged-in `claude` CLI as a model backend, so requests run on your Claude subscription instead of a pay-per-token API key. It is off by default and meant for personal setups: start Agenvoy with `agen --enable-claude-code` (the daemon must be stopped first with `agen stop`) to enable it for that TUI and the daemon it starts. Since v1.2.0 the daemon runs every agent, so the `claude` processes live in the daemon; a TUI started later without the flag follows the daemon's setting.
| Aspect | Behavior |
|---|---|
| Transport | One `claude -p` process per session, model and system prompt, speaking `stream-json`. Since v1.1.3 the process is keyed by a hash of the system prompt, so summary, dispatch and follow-up calls no longer restart the main loop's process and drop its cache, and a call without tools inside a session gets its own persistent process instead of a one-off run. The CLI's own tools and MCP servers are disabled (`--tools ""`, `--strict-mcp-config`); tool calls are executed by Agenvoy under its usual rules. Since v1.1.4 the provider lives in `go-llm-router` instead of this repository; Agenvoy passes in the enable flag, the sessions directory for state files and the cache TTL |
| Tools | Only the tool names plus the full `find_tools` schema are sent; every other schema is fetched on demand through `find_tools`. Since v1.1.3 every tool except `find_tools` and client tools is marked as a stub on each request: calling one before fetching its schema does not run it — Agenvoy returns the schema and asks for the call again. Unlike other providers it does not go through `run_tool` (v1.2.0): the tool list it receives excludes `run_tool` and tools are called by their own names |
| Models | Since v1.1.2 listed by the `claude` CLI itself: Agenvoy sends an `initialize` control request to `claude -p` (no tools, no MCP, no session persistence) and keeps each distinct `resolvedModel`, so the list is what your subscription can actually run. Before v1.1.2 it was read from `models.agenvoy.com`, the `claude` catalog. A trailing `-YYYYMMDD` date is dropped when the context window is looked up |
| Routing | Inside a tier, `claude-code` models rank ahead of every other provider, then `codex`; for the same base model under several providers, `claude-code` comes first. Since v1.1.1 a model named in the request, or the previous model kept for an unchanged subject, stays ahead of this reordering |
| Quota | Parsed from `claude -p /usage` — no API call — and shown with `Shift+U` and in `GET /v1/providers/quota` |
| Usage | Input tokens include cache writes, the same as for `claude` |
| Sessions | Since v1.1.1 each session + model keeps its own Claude Code session; since v1.1.2 only while the session runs on the `1h` prompt cache (both model and reasoning pinned): the first turn starts `claude` with `--session-id`, later turns reuse the live process or, after it exits or the daemon restarts, `--resume` it; if resuming fails a fresh session is started with the full history. State lives in `sessions//.claude_code/_.json` (`_plain` appended for calls without tools; `sessions//claude_code_.json` before v1.1.3). Under the `5m` cache — and for calls without a session (before v1.1.3 also calls without tools) — `claude` runs with `--no-session-persistence` and only a live process is reused — nothing is saved or resumed, so a session on `auto` no longer leaves a Claude Code session that would miss the cache anyway |
| Prompt cache | Since v1.1.1 `CLAUDE_CODE_PROMPT_CACHE_TTL` is `5m` when the session's model or reasoning is `auto` (and for one-off calls), `1h` when both are pinned; a TTL change restarts the process |
| Tool call format | `` blocks, and since v1.1.1 also `` with `` children (namespaced tags included); since v1.1.4 a JSON body containing a literal `` is read to its end instead of being cut at that string. A reply that is only empty tags plus tool calls is shown as `Processing` (`Processing...` before v1.1.4) |
| When not enabled | Since v1.1.2 the `/model add` row is hidden unless `--enable-claude-code` is on and the `claude` binary is found (it used to appear regardless). Registered `claude-code@` models are skipped by the agent registry, routing, the `/model` list and the dispatcher picker; `GET /v1/providers` marks the row `hidden` |
---
Source: https://agenvoy.com/docs/providers-compat.md
# Ollama & OpenAI-Compatible Endpoints
Using any OpenAI-compatible `/v1` endpoint as a backend: how to add one, where its URL and key live, and what has been tested.
## Anything OpenAI-compatible
Beyond those eleven, the `compat` entry (**Local/Custom**) is not a twelfth vendor — it is the escape hatch that makes the list open-ended. Point it at any OpenAI-compatible `/v1` base URL, with an optional key, and it becomes a usable backend: Ollama, LM Studio, vLLM, LiteLLM, a self-hosted gateway, a vendor that shipped an OpenAI-shaped API last week. Register as many as you need — each one is its own named entry.
Two local endpoints are built in (`configs/jsons/local_compat.json`): **Ollama Local** at `http://localhost:11434/v1` and **Llama.cpp Local** at `http://localhost:8080/v1`. `/model add` probes both with `GET /models` in parallel (500 ms budget) and appends each one that answers to the end of the **Custom** tab (since v1.0.22), going straight to model selection with nothing written to `config.json`. Endpoint models are registered as `@` with the endpoint name lowercased (`ollama@gemma3:4b`); the older `compat[NAME]@` form is still accepted and rewritten whenever `config.json` is loaded or saved.
Model lists are fetched live when a model is added (TUI `/model` → `add`, or `GET /v1/provider/:provider/models`); for a local or custom endpoint the list comes from the endpoint's own `GET /models`. There are no static model catalogs in the repository.
## Adding a custom OpenAI-compatible endpoint
Use **Local/Custom** (`compat`) and point it at any endpoint that accepts the OpenAI Chat Completions schema. URL convention follows Zed: **enter the URL up to `/v1`** (e.g. `http://192.168.1.10:4000/v1`); the router appends `/chat/completions`. The built-in Ollama and llama.cpp ports need no entry.
### Storage split (URL vs key)
| What | Where | API |
|---|---|---|
| URL | `~/.config/agenvoy/config.json` `compats[]` — `{provider, url}`, provider name uppercased | `config.UpsertCompat` / `config.GetCompatURL` (`internal/session/config`) |
| API key | OS keychain | `keychain.Set("COMPAT__API_KEY", value)` |
`GetCompatURL` checks `compats` first, then the built-in local endpoints. There is no `COMPAT__URL` keychain key — it was removed after a bug where the TUI wrote the URL to config while the runtime read the keychain and always fell back to localhost.
### Tested compat targets
| Target | Works | Notes |
|---|---|---|
| Ollama | Yes | built in at `http://localhost:11434/v1` |
| llama.cpp server | Yes | built in at `http://localhost:8080/v1` |
| LM Studio | Yes | |
| vLLM | Yes | `--enable-auto-tool-choice --tool-call-parser ` for tool use |
| LiteLLM proxy | Yes | virtual key as Bearer token |
| Groq / Together / DeepInfra / Fireworks | Yes | |
| Azure OpenAI | No | needs an `api-key` header (not `Bearer`) plus `?api-version=` — not supported |
| opencode Go (`opencode.ai/zen/go/v1`) | No | requires a per-conversation `x-opencode-session` header; missing it returns `400 MissingSessionID` — not supported |
### What counts as compatible
A target qualifies when the request body and `Authorization: Bearer ` are the whole contract. Agenvoy carries no vendor-specific headers through the compat channel, so an endpoint that demands one is out of scope regardless of how OpenAI-shaped its body is. Only three kinds of backend are in scope: vendors that serve their own models, OpenAI-compatible NIM, and Cloudflare.
opencode Go is the working example of the boundary. Since its [2026-09-03 announcement](https://x.com/opencode/status/2095410501400289576) the gateway rejects any request without `x-opencode-session`, a private header whose value has to change per conversation so the request lands on the node holding that conversation's cache. Chat Completions is stateless — the conversation lives in `messages` and the protocol has no session concept — so requiring the client to track and rotate a vendor header puts the endpoint outside the compatible set. Opening the compat channel to custom headers for one vendor would open it for every vendor, so it stays closed.
---
Source: https://agenvoy.com/docs/providers-config.md
# Managing Models
Every model and credential task, with its TUI command and HTTP endpoint.
Everything is managed from the TUI or the local HTTP API — there is no `agen model` CLI subcommand:
| Task | TUI | HTTP |
|---|---|---|
| Add / remove a provider or model | `/model` → `add`; `d` on a model row removes it. Since v1.1.2 the model list shows each model's context window as input/output tokens (`200K/64K`) when the window table knows it, and already-registered models are only preselected — the `(added)` suffix is gone. Since v1.2.0 the model multi-select has a search field that filters the list as you type | `POST` `/v1/models`, `DELETE` `/v1/models/*name` |
| Reorder fallback priority | `/model` → `model`, `w` / `s` move the model under the cursor. Since v1.2.0 each moved row shows its offset (`(+1)` / `(-1)`) and the order is saved only when the popup is confirmed with Enter; leaving with Esc discards it | `GET` `POST` `/v1/model/priority` — `{models}` |
| Set a model's tier | `/model`, `t` on a model row | `POST` `/v1/model/tier` — `{model, tier}` |
| Pick the dispatcher model | `/model` → `dispatch` | `GET` `POST` `/v1/model` — `{dispatcher}` |
| Pick a session's reasoning level | `/model` → `reasoning` (or `Shift+A` / `Shift+D`) | `POST` `/v1/session/:id` — `{reasoning}` |
| Pick the summary model | `/model` → `summary` | `GET` `POST` `/v1/model` — `{summary}` |
| Pick the image generator | `/model` → `image` | `GET` `POST` `/v1/model` — `{image}`, a provider endpoint rather than a model name |
| Pick the speech-to-text model | `/model` → `stt` | `GET` `POST` `/v1/model` — `{stt}`, chosen from `GET /v1/model/audio` |
| Pick the text-to-speech model | `/model` → `tts` | `GET` `POST` `/v1/model` — `{tts}`, chosen from `GET /v1/model/audio` |
| Pick a session's model | `/model` (or `Shift+W` / `Shift+S` to cycle `auto` and registered models) | `POST` `/v1/session/:id` — `{model, reasoning}` |
| Store a credential | `/key` | `POST` `/v1/provider/:provider/key` |
| OAuth login | `/model` | `GET` `/v1/provider/:provider/oauth` (SSE: device code for `copilot`, browser URL for `codex` / `grok-oauth`, CLI check for `claude-code`) |
| Clear an OAuth login | `/model` | `DELETE` `/v1/provider/:provider/oauth` |
| Check quota / balance | `Shift+U` | `GET` `/v1/providers/quota`; one model's provider: `GET` `/v1/model/quota?model=` |
Model routing is one object: `GET` / `POST` `/v1/model` reads and partially updates `dispatcher`, `summary`, `image`, `stt`, and `tts` together. A field left out is untouched, `""` clears it. In `config.json` these land as `dispatcher_model`, `summary_model`, `image_generator`, `stt_model`, and `tts_model`.
Audio routing is separate from the model registry. `stt` and `tts` are not picked from the models you registered with `/model add` — they are queried live from whichever of OpenAI, Gemini and OpenRouter currently hold a credential, and only those three providers back audio today (OpenRouter was added in v1.0.17). Selecting `disable` (listed first) for `tts` also removes `generate_audio` from the tool set; selecting `disable` for `stt` stops `read_files` from transcribing audio and video, and makes inbound Telegram / Discord voice messages refuse with a hint to pick a model.
Credentials (API keys, OAuth tokens) live in the OS keychain under service `agenvoy`, never in plain JSON. `config.json` keeps only the list of stored key names under `keys`.
The daemon watches `config.json`; a write reloads the agent registry (and reconnects Telegram / Discord) without a restart.
Since v1.0.13 a registry entry holds only the model name and resolves its provider config on every `Send` instead of building the client once at startup, so an OAuth token refreshed in the keychain takes effect on the next request rather than at the next registry reload.
---
Source: https://agenvoy.com/docs/providers-routing.md
# Model Routing
How fallback priority, tiers and the dispatcher model decide which model answers a request.
## Model priority and tiers
Registered models are stored as an ordered list under `models` in `config.json`. That order is the fallback priority: after the selected model fails, every other entry is tried top to bottom — `pass` models excluded — and the last non-`pass` entry is the final line of defense. Fallback skips models on the failed model's provider, health-checks each candidate (10-second timeout) and takes the first that answers, walks the list up to four rounds, and is off for a session pinned to a model. Skipping models whose context window cannot hold the input was removed in v1.2.1. Before v1.0.20 `pass` models were always moved to the end; from v1.0.20 to v1.0.27 fallback still tried them at their place in the order. `POST /v1/model/priority` moves the listed names to the front in the given order and keeps the rest after them.
Each model can carry a tier in `model_tag` (`{"@": ""}`):
| Tier | Meaning |
|---|---|
| `S` | Strongest — code and work that asks for depth or precision |
| `A` | Default for most work, one step below the flagship |
| `B` | Mainstream mid tier |
| `C` | Fast and cheap; calls tools reliably as instructed |
| `pass` | Never picked by auto routing, subagents or fallback, even when the request names it (since v1.1.0); used only when a session is set to it |
An untiered model follows the built-in naming rules: S = `claude-fable`, `claude-opus`, `gpt-*-astra`; A = `gpt-*-sol`, `grok-4.5+`, `claude-sonnet`, `gpt-*-terra`, `gemini-*-pro`, `deepseek-pro`, `glm`, `kimi`; B = `claude-haiku`, `gpt-*-luna`, `gemini-*-flash`, `grok` below 4.5, `deepseek`; C = `*-mini`, `*-nano`, `gemini-*-flash-lite` and open-weight models (`gemma*`, `gpt-oss`, `qwen*`, `llama*`). v1.0.19 moved `gpt-*-sol` and `grok-4.5+` from S to A. Tiers are read per request, so a change applies without a restart.
## Dispatcher model
The dispatcher LLM decides which worker model handles each task. It runs in `exec.Run` through `exec/model.Route` → `selectWithLLM` (or `selectWithJev` for TypeSafe), before `Execute()` enters its iteration loop, receiving the registered model list, every registered model grouped by its resolved tier (user-set tier first, then the naming rules), the work-kind table below, the user input, and a hint about any matched skill. Its routing call is issued at `ReasoningNone` with a 30-second timeout.
Routing is skipped when it cannot matter: a model named explicitly by the caller is used as-is (and fails if unregistered), a session bound to a model other than `auto` uses that model, and a registry with only one selectable model returns it directly. In these cases the selector is still called when the session's reasoning is `auto`, but only for the reasoning level.
The dispatcher answers with the work kind on its first line and a comma-separated list of model names on the second (since v1.1.0), so the work kind also sets the reasoning level for sessions whose reasoning is `auto` — no TypeSafe call needed. The first name that is registered, not `pass` and not cooling down becomes the primary model. Since v1.0.20 the rest of that list no longer decides fallback — fallback follows the priority order above. It classifies the request into one of the five work kinds in the table under TypeSafe below and walks that kind's tier order. Since v1.0.21 the LLM dispatcher, the TypeSafe classifier and the subagent planner read one shared work-kind table (`internal/session/config/tier.go`) and one naming rule, so they rank the same way; before that the LLM dispatcher had no `research` / `work` split and sent everything outside code, chat and fetch to S > A > B > C. The dispatcher is told never to return a `pass` model, and a `pass` name it returns anyway is dropped (before v1.1.0 a request could still name one). Inside a tier, models on the subscription providers `claude-code` and then `codex` rank ahead of every other provider. When the same base model is registered under several providers, the list prefers `claude-code`, then `codex` / `grok-oauth`, then `copilot`, then the direct API, then `openrouter`. The `subagent_dispatch` topic of `reasoning_guide` gives the main agent the same table one tier lower (S→A, A→B, B→C) as a reference for each leg's `model`, so a leg of `code` work reads A > B > C > S. If the dispatcher call fails, the next dispatcher candidate is chosen by a fixed provider ranking; if none answers, the priority order alone decides.
Set the dispatcher with `/model` → `dispatch` in the TUI, or `POST /v1/model` with a `dispatcher` field.
---
Source: https://agenvoy.com/docs/providers-typesafe.md
# TypeSafe Dispatcher (beta)
The beta classifier that picks models by work kind instead of asking an LLM.
v1.0.18 added a second routing backend. With `dispatcher_beta` on, routing is a single classification call to TypeSafe (`https://api.typesafe.ai/v1/systemone`, model `jev-latest`) instead of a prompt to one of your own models, so no registered model is spent on routing. It needs a `TYPESAFE_API_KEY` in the keychain — the TUI asks for one when the toggle is turned on, and `POST /v1/model` returns 400 with `missing_key` when it is absent. Keys come from the TypeSafe Console (`https://console.typesafe.ai/keys`).
The call asks up to three questions about the request: what kind of work it is, whether it names a specific non-`pass` model, and — when the session has earlier turns — whether it continues the previous subject. It has 3 seconds to answer. The work answer picks the tier order and the reasoning level:
| Work | Tier order | Reasoning |
|---|---|---|
| `code` | S > A > B > C | `xhigh` |
| `research` | S > A > B > C | `high` |
| `work` (the default for anything else) | A > S > B > C | `medium` |
| `chat` | B > C > A > S | `none` |
| `fetch` | C > B > A > S | `low` |
A model named in the request goes first; when none is named and the subject is unchanged, the session's previous model takes that slot so its prompt cache is reused. When the previous model leads this way, the reasoning level stays at the previous one — since v1.1.1 whenever it is a valid level, not only when it is higher — and since v1.1.1 these leading picks are no longer reshuffled by provider priority; only the ranked candidates after them are. The previous model is recorded after each successful reply and expires after 30 minutes for `openai` / `codex`, 60 minutes for `gemini`, and 5 minutes for other providers. The remaining candidates are sorted by tier, reading `model_tag` first and falling back to the naming rules above; within a tier, `claude-code` and then `codex` models come first, then ties break by model family in the order the naming rules list them. For `research` work, `copilot@` models move behind the others of the same rank because of Copilot's smaller context window (since v1.0.26). `pass` models are left out of this ranking. The request context is the last 4 user and assistant turns, each cut at 2,048 characters (6 turns at 2,000 before v1.0.19). If the call fails or times out, routing falls back to the LLM dispatcher.
Turn it on with `/model` → `dispatch` → **TypeSafe/Jev(beta)** in the TUI, or `POST /v1/model` `{dispatcher_beta: true}`. Picking an ordinary model as the dispatcher turns it back off.
---
Source: https://agenvoy.com/docs/providers-reasoning.md
# Reasoning Levels and Fast Mode
The shared reasoning scale, how each session sets its level, and the fast-mode toggle.
## Reasoning levels
`go-llm-router` normalizes reasoning onto one scale — `none`, `low`, `medium` (default), `high`, `xhigh`, `max` — and maps it per provider (Claude thinking budgets, Gemini thinking budgets, OpenAI effort, ...). Aliases `minimal`, `extra`, and `ultra` map to `low`, `xhigh`, and `max`. Levels outside a model's supported range are clamped rather than rejected.
The level is passed explicitly through each `Send` call; there is no global reasoning setting in `config.json`. Each session has its own level — `auto` (the default) or a fixed one — set with `/model` → `reasoning` or cycled with `Shift+A` / `Shift+D` in the TUI, `auto` included. With `auto`, the level comes from the selector's work kind (the LLM dispatcher or TypeSafe, whichever runs), falling back to `medium` when it returned none; this also applies to a session pinned to a model. A level passed with the request (for example `reasoning_effort`) wins over both.
**Auto reasoning** (`auto_reasoning`, v1.0.18 to v1.0.27) was a global TypeSafe-only toggle; it was removed in v1.1.0 and replaced by the per-session `auto` level above. The `auto_reasoning` field of `POST /v1/model` and `GET /v1/session/:id` is gone.
## Fast mode
`Shift+F` toggles fast mode, which passes `core.ModeFast` through the router so supported backends request a faster service tier. Support is model-specific (`core.SupportFast`) — for example recent OpenAI generations, Claude Opus 4.8 / Opus 5, most Grok models, and selected Gemini families. Unsupported models silently fall back to the default tier. Fast mode is process-local and not persisted; since v1.2.0 the TUI sends its fast-mode state with each run to the daemon, so the toggle applies to that window's runs only.
---
Source: https://agenvoy.com/docs/cli-commands.md
# CLI Commands
## Top-level dispatch
`agen` parses `os.Args[1]` and dispatches to one of the subcommands below. With no subcommand it attaches the TUI (and fork-execs a daemon if none is running); with a non-TTY stdin it serves MCP JSON-RPC over standard I/O instead. Since v1.2.0 the TUI runs no agents itself: it sends every request to the daemon over `~/.config/agenvoy/daemon.sock` and reconnects every 5 seconds while the daemon is down.
```bash
agen # Attach TUI; spawn daemon if not running
agen stop # Stop the running daemon
agen update # Download the latest release and rebuild
agen --enable-claude-code # Attach TUI with the claude-code provider enabled (daemon must be stopped)
agen --daemon [--enable-claude-code] # Start the daemon directly (optionally with claude-code)
```
That is the whole surface. Anything else prints the usage block and exits 1. The one-shot `agen cli ` / `agen run ` forms were removed — a single request now goes through the TUI, the local HTTP API (`POST /v1/send`), or a chat channel.
`agen --enable-claude-code` (v1.1.0) turns on the `claude-code` provider for the daemon it starts; it refuses to run while a daemon is alive, so run `agen stop` first.
The short-lived `agen startup ` subcommand was removed; toggle launch-on-login with **Startup on login** in the TUI `/config`.
Model, session, and MCP management live in the TUI (`/model`, `/session`, `/new`, `/mcp`), the web dashboard at `http://127.0.0.1:17989`, and the local HTTP API — there are no `agen model` / `agen session` / `agen mcp` subcommands.
### `agen stop`
SIGTERM the running daemon, then clear its runtime record. Prints `No daemon running.` and exits 0 when nothing is alive; a stale PID record is cleared instead of killed.
### `agen update`
Always-overwrite update to the latest release. Downloads `https://agenvoy.com/scripts/update.sh` into `agenvoy-update-*.sh` in the system temp directory (`$TMPDIR`), executes it with `bash`, and removes the temp file on completion (SIGINT / SIGTERM also clean up). The running daemon keeps the old binary inode — run `agen stop` and re-attach to pick up the new build.
## Modes
Press `Shift+Tab` in the TUI to toggle auto mode (`safe` ↔ `auto`), `Shift+F` for fast mode, `Shift+G` for `CLAUDE.md` / `AGENTS.md` injection (v1.2.0). Auto mode is session-local and resets on TUI restart. `POST /v1/send` has no `allow_all` field; auto mode is a TUI toggle only. See the TUI Guide for the full shortcut and slash-command list.
## Configuration
Runtime limits live in `~/.config/agenvoy/config.json` under `limits`; there are no environment-variable overrides. See the Configuration page.
---
Source: https://agenvoy.com/docs/cli-commands-make.md
# `make` Shortcuts
The `make` targets for building and running Agenvoy from source.
```bash
make build # Compile and install to /usr/local/bin/agen (needs sudo)
make app # stop + build + attach TUI
make dev # stop + build, then agen --enable-claude-code serving page/ from disk (AGENVOY_PAGE_DIR)
make stop # Stop the running daemon
make update # = agen update
make test # Run all unit tests
```
`make build` stamps the current exact-match git tag into `internal/runtime.CurrentVersion` (`(dev)` when HEAD carries no tag); `GET /v1/info/version` reads it back.
`make build` also refreshes the bundled skills and tools: `extensions/skills/` → `~/.config/agenvoy/skills/.system/` and `extensions/scripts/` → `~/.config/agenvoy/tools/.system/`. Since v1.0.17 `code-reviewer`, `commit-generate`, `readme-generate` and `version-generate` are no longer bundled; install them with `/skill` → `system` in the TUI (`/skills` before v1.0.22).
---
Source: https://agenvoy.com/docs/cli-commands-prefixes.md
# Input Prefixes
Prefixes you can put in front of a message to change how it is handled.
One prefix is recognized, in `exec.Prepare()`:
| Prefix | Effect |
|---|---|
| `/` | Activate a matching skill; the remaining text becomes its binding context |
```
/commit-generate
```
The `:self-id` and `model:` prefixes were removed; switch sessions with `/session` and choose models with `/model` or the `session_id` / `model` fields on `POST /v1/send`. External-CLI prefixes (`/claude`, `/codex`, `/gemini`, `/gh`) were removed along with the external agent invocation tools.
---
Source: https://agenvoy.com/docs/tui-guide.md
# TUI Guide
Single bubbletea textarea (`internal/runtime/tui`); slash commands open transient popups that close cleanly back to the prompt.
Since v1.2.0 the TUI no longer runs agents itself: the daemon runs every request and the TUI is a client over the Unix socket `~/.config/agenvoy/daemon.sock` (mode `0600`, newline-delimited JSON frames). A run is bound to the connection that started it, so closing the TUI stops the run while its pending snapshot is kept. Confirmations and `ask_user` questions from that run go only to the TUI window that started it; a restricted-path confirmation asks for the system password in a TUI password field and the daemon verifies it (`sudo -S -v`). When the socket is down the status line shows `Connecting` and the TUI retries every 5 seconds; plain input, skills, `/pending` resume and scheduler dispatch wait until it reconnects, while local `/` commands keep working. When the agent changes directory the daemon pushes the new work directory to the TUI.
## Shortcuts
| Key | Action |
|---|---|
| `Enter` | Send the current input. While a run is in flight only plain text is sent, as a **steer** message delivered to the running loop; `/` commands are neither listed nor sent until the run ends |
| `Alt+Enter` | Insert a newline |
| `/` | Begin slash-command filter (popup picker — Up / Down to navigate, Tab / Enter to autocomplete into the textarea, Esc to dismiss) |
| `Up` / `Down` | Walk input history when the cursor is on the first / last line (per-session `.cmd_history` file) |
| `Esc` | Outside a popup, in this order (v1.2.0): dismiss the command picker, else close the notice box, else open the cancel popup while running — **No**, **Yes (cancel)**, or **Pause (keep pending)**. Cancel discards the pending task, pause stops the run and leaves it resumable through `/pending`. A run that has emitted nothing yet is cancelled straight away with no popup. Otherwise clear a non-empty input |
| `Ctrl+C` | While running, cancel the task outright (the user-cancel path — the pending task is discarded); otherwise print `ctrl+c again to quit`. A second `Ctrl+C` within 3 seconds quits the TUI |
Single-key toggles fire only when the input is empty, the command picker is closed and no run is active (`Shift+Tab` only checks the last two):
| Key | Action |
|---|---|
| `Shift+Tab` | Toggle auto mode (`safe` ↔ `auto`) |
| `Shift+F` | Toggle fast mode — the mode line shows `[fast]` while enabled |
| `Shift+G` | Toggle `CLAUDE.md` / `AGENTS.md` injection (v1.2.0; off at start). When on and the work directory has one of those files, the mode line shows `[CLAUDE.md]` or `[AGENTS.md]` and the file is added to each turn's context |
| `Shift+U` | Live provider usage / balance for every registered provider that reports one: `codex`, `grok-oauth`, `copilot`, `deepseek`, `openrouter`, `claude-code` (Ollama Cloud: Removed in v1.2.0); the result lands in the notice box. Since v1.1.3 a provider with several quota windows shows each one as `5h/week/total` percentages, colored by its own level |
| `Shift+W` / `Shift+S` | Cycle the session model backward / forward through `auto` and the registered models (not the same as `w` / `s` inside `/model`, which reorder the fallback list) |
| `Shift+A` / `Shift+D` | Cycle the session reasoning level down / up through `auto`, `none`, `low`, `medium`, `high`, `xhigh`, `max` — `auto` included since v1.1.0 |
Shell command mode (`Shift+T`) was removed; ask the agent to run the command instead. The `Shift+M` model list was removed; use `/model`, which lists the registered models.
In list popups, `Enter` confirms the highlighted entry, `d` deletes it and `t` sets its tier where the popup offers those actions. Text-input popups use `Ctrl+S` to confirm, `Enter` for a newline, and `Esc` to cancel.
The TUI auto-tails the active session's `action.log` (foreign-process writes prefixed with `▌ ` in warn-purple). Single-session view only.
Since v1.1.0 status messages (command results, errors, `Shift+U` output) collect in a framed notice box above the input instead of scrolling into the transcript. Since v1.1.3 plain success confirmations — a saved role, a deleted key, a new session and the like — go to the debug log instead, so the box only holds what needs attention. It keeps the last 32 lines and shows 4 at a time; `Tab` pages up when there are more, and `Esc` closes it.
---
Source: https://agenvoy.com/docs/tui-guide-modes.md
# TUI Modes
The input and display modes the TUI switches between.
The current mode renders on the line below the input box, followed by the working directory. Since v1.2.0 the labels are bracketed again (`[fast][safe]`), and `[CLAUDE.md]` or `[AGENTS.md]` follows the mode while `Shift+G` guide injection is on and the work directory has that file. While the daemon socket is down the line shows `Connecting` instead:
- `safe` (default) — tool calls require user confirmation before execution
- `auto` — all tool calls are automatically approved (`allowAll = true`); sandbox and validator still apply
- `fast` — passes `core.ModeFast` through `go-llm-router` so supported provider backends request a faster service tier
The session's model and reasoning level (`model/reasoning`, where reasoning may read `auto`) sit on the right of the input box's bottom border. The session name is no longer shown there; `Session ID: ` is part of the header block printed at startup and after each session switch.
The global auto reasoning toggle was removed in v1.1.0. Reasoning is now a per-session level, and `auto` is one of its values: the agent selector then picks the level from the kind of work it reports.
Since v1.0.21 the transcript prints `⏵ Model()` when a run picks its model and `⏵ Switch()` when fallback moves to another one; the thinking line no longer repeats the model. Compaction shows as `⏵ Compact(tool history)` or `⏵ Compact(conversation history)`. The reply footer is two lines: duration, tokens and finish time first, then `model/reasoning` and the session label. Since v1.1.0 the footer no longer carries the quota; press `Shift+U` for it. Finish times are shortened — `15:04` for today, `01-02 15:04` earlier this year, the full date otherwise; the web chat footers drop today's date and the current year the same way.
While a `write_todo` plan is open, the `Plan (done/total)` header in the thinking area animates its mark with the spinner until every item is done (v1.1.0).
Auto mode is session-local and resets when the TUI restarts. Fast mode and guide injection are process-local, sent with each run to the daemon, and are **not** persisted to `config.json`.
---
Source: https://agenvoy.com/docs/tui-guide-commands.md
# Session and Model Commands
Slash commands for models, sessions, roles, history and usage.
| Command | Description |
|---|---|
| `/model` | Seven tabs since v1.1.0 (←/→ switches): `model`, `reasoning`, `dispatch`, `summary`, `image`, `stt`, `tts`. `model`: registered models in fallback order (Enter picks the session model, `d` removes one, `t` sets its tier `S` / `A` / `B` / `C` / `pass`, `w` / `s` moves it up / down the fallback order) plus `add` (model from a provider). Since v1.2.0 a move only marks the row with its offset (`(+1)` / `(-1)`); the new fallback order is saved when you confirm with Enter. The multi-select model list in `add` has a search field (v1.2.0). `reasoning`: the session level, `auto` or a fixed one. `dispatch`: a registered model or `Jev` (the TypeSafe CLM classifier). `summary`: the summary-memory model, or `auto` (listed first) to clear it so summaries use the main model. `image` / `stt` / `tts`: `disable` plus every model or provider with credentials, searchable when OpenRouter models are listed. Each tab and `add` also work as a subcommand, e.g. `/model add`, `/model reasoning`. The v1.0.22 two-tab layout (`model` / `config`) and its auto reasoning on / off switch are gone |
| `/session` | Renamed from `/sessions` in v1.0.22, and absorbs the former `/bot`. Session picker — when sessions from more than one origin exist, tabs split them by ID prefix (`all`, then `cli-`, `tg-`, `dc-`, `chat-`, `temp-`); Enter switches, `d` deletes (since v1.1.1 deleting a session other than the current one keeps the picker open); below the list, `name` / `self id` / `role` edit the current session's display name, self id and system prompt. `/session ` switches directly; `/session name`, `/session id`, `/session role` open one field directly |
| `/new` | Create a session. `/new [name]` goes straight to the session description: pick a role (see `/role`) or **Custom** to write one. Since v1.1.0 there is no separate name prompt; without a name the session is named after its short id |
| `/compact` | Remove redundant exchanges from history through LLM analysis — confirmation required. `/compact` and `/reset` share one popup (since v1.1.0); since v1.1.1 it has three tabs, `summary`, `compact` and `reset`, and `/compact` opens on `compact`. `summary` is read-only: the session's `summary.json` laid out field by field, or `no summary yet` |
| `/reset` | Reset the current session — opens the `reset` tab: regenerate the summary first and keep it, or reset everything including the summary; history, task history and `action.log` are dropped. Since v1.2.0 the reset progress and result notices go to the debug log only |
| `/role` | Renamed from `/rule` in v1.1.0. List / add / edit a role — title + description. Roles are the session prompts `/new` offers |
| `/resume` | Reload the visible transcript — last 100 entries from `action.log` |
| `/usage` | Per-model token usage — current session above, global below — 24 h / 7 d / 28 d |
| `/pending` | List / resume interrupted tasks — error recovery and `ask_user` resume. Since v1.2.0 the resume runs in the daemon (a `pending` frame) and any open questions are asked inline; it waits while a run is active or the daemon is offline |
| `/clear` | Clear the visible transcript — memory untouched |
There is no `/switch`; use `/session`. There is no `/dangerous`; delete a session with `d` in `/session` and mark skills in `/skill` → `permission`. v1.0.22 renamed `/sessions` → `/session` and `/skills` → `/skill`, folded `/bot` into `/session` and `/allow-skill` into `/skill`; popups now open full-screen and switch tabs with ←/→. There is no `/memory`; use `/compact` and `/reset`. There are no `/discord`, `/telegram` or `/admin-channel`; use `/channel`. There is no `/startup`; use `/config`. There is no `/rule`; v1.1.0 renamed it `/role`. There is no `/note`; operator notes were removed in v1.0.23 — keep reference material in files and search them through KuraDB instead. There are no `/cron` or `/task`; use `/schedule`.
---
Source: https://agenvoy.com/docs/tui-guide-commands-more.md
# System and Integration Commands
Slash commands for MCP, skills, channels, settings, schedules, credentials and the TUI itself.
| Command | Description |
|---|---|
| `/mcp` | List MCP servers · add · per-server login (browser OAuth), reconnect, tools, per-tool permission, remove. Since v1.2.0 status, reconnect, the tool list and disconnect-on-remove go through the daemon; the TUI opens no MCP connections of its own |
| `/skill` | Replaces `/skills` and `/allow-skill` in v1.0.22; three tabs. `permission`: every scanned skill with its source, Enter toggles it between `always allow` and `ask` in the global list (`~/.config/agenvoy/allow_skill`), `o` opens its folder; the project scope is no longer offered in the TUI (use `POST /v1/allowlist`). `system`: multi-select over the installable skills (`code-reviewer`, `commit-generate`, `go-test-generate`, `readme-generate`, `seo-optimize`, `version-generate`, `wiki-generate`); checked entries are cloned from `github.com/agenvoy/skill-` into `~/.config/agenvoy/skills/.system_design/`, unchecked ones are removed. `source` (v1.2.0): multi-select over the skill sources `claude`, `codex`, `copilot`, `opencode`, `agents` (`~/./skills`; `claude` also covers `/.claude/skills`); unchecked sources are saved to `skill_source_disabled` in `config.json` and skipped when scanning. The `openai` source (`~/.openai/skills`) was Removed in v1.2.1 |
| `/channel` | Two tabs since v1.1.0, `telegram` and `discord` (←/→; `/channel discord` opens the second). A channel that is not connected asks for its token (validated on enable); a connected one lists its authorized chats — `d` revokes one — plus `disable`. The `admin` relay moved to `/config` |
| `/config` | Searchable settings: **Startup on login** (launchd agent on macOS, systemd user unit on Linux), **Reply language**, **Output dir**, since v1.1.0 **Admin Channel** (the authorized chat that receives new-chat verification codes; `disable` keeps them in the log only), and since v1.2.0 **Official guide** (inject the vendor prompting guide for the active model; enabled by default, stored as `official_guide_disabled`) |
| `/schedule` | Recurring (cron) and one-shot (task) entries in one list — Enter fires one now, `d` deletes it. Add or edit schedules by asking the agent |
| `/sched-` | Manually dispatch an existing scheduler skill body. Filtered by session — only skills bound to the current session's task / cron entries appear |
| `/update` | Fetch the latest release, rebuild, and quit the TUI (re-attach with `agen`) |
| `/log` | Follow the raw `daemon.log` through `$PAGER` (`less +F`); Ctrl-C stops following |
| `/key` | Update / rotate a keychain value, picked from the recorded key list |
| `/exit` | Exit the TUI (daemon keeps running); `/quit` is an alias |
There is no `/kuradb`. The setup / update / reconnect wizard was removed in v0.35.0 — KuraDB is installed on its own and registered like any other MCP server through `/mcp`.
There is no `/voice`, removed in v0.34.3. Inbound voice handling is no longer a toggle: it follows whichever speech-to-text model `/model stt` selects, and outbound voice replies are no longer synthesized by the channels themselves — `generate_audio` writes a file instead, and since v1.0.13 an `.ogg` / `.oga` / `.opus` file attached to a Telegram reply is delivered as a voice note.
There is no `/sudo`. Elevated mode was removed in v0.32.10 in favor of per-request authorization — a restricted path or a write outside `$HOME` raises one prompt for that call and asks for the system password there. Since v1.2.0 the TUI takes the password in a password field after **Yes** and the daemon verifies it with `sudo -S -v`. See the Security page.
Loaded skills appear in the same picker below the built-in commands, labeled with their source; scheduler skills (`/sched-`) follow them.
---
Source: https://agenvoy.com/docs/rest-api.md
# REST API
The daemon listens on the loopback addresses only — `127.0.0.1:17989` and `[::1]:17989` — so LAN clients cannot reach it. The port is fixed and not configurable.
The same daemon serves the web dashboard at `/`. The dashboard is embedded into the binary at build time, so `http://127.0.0.1:17989` is the whole surface — there is no hosted front-end. `AGENVOY_PAGE_DIR` swaps the embedded copy for files on disk during development (`make dev`).
Since v1.0.1 the dashboard also runs offline. `GET /sw.js` serves a service worker that precaches the embedded assets under a versioned cache; `GET /vendor/*` serves the third-party assets (QuickUI, Font Awesome, Material Symbols, and the nanomd / voice scripts) from `~/.config/agenvoy/vendor/`, which the daemon downloads once at startup and re-downloads when the binary version changes. Nothing is fetched from a CDN at page load. In `AGENVOY_PAGE_DIR` mode `/sw.js` instead serves a teardown worker that clears its caches and unregisters itself, so a dev build is never served from a stale cache.
Endpoints marked **local** additionally require the request to originate from `127.0.0.1`/`::1` (the `localhostOnly()` guard). They manage credentials, config files, or process lifecycle and are meant for a same-machine dashboard, not remote clients. Unmatched routes carry the same guard: an unknown `/v1/` path returns a JSON 404, and any other unknown path falls back to the dashboard index.
## Agent execution
| Method | Path | Description |
|---|---|---|
| `POST` | `/v1/send` | Run an agent request |
| `POST` | `/v1/chat/completions` | Stateless OpenAI-compatible chat completion |
| `GET` | `/v1/info/version` | Build version stamped at compile time (`{version, dev}`); `dev` is true for an untagged build |
| `GET` | `/v1/log` | SSE stream. With no query it carries daemon `slog` records only (`EventDaemonLog` frames, `source` = level) — the same feed the TUI header shows. It includes new-chat verification codes, so daemon frames are attached only for loopback callers. `?sessions=a,b` adds those sessions' events on the same connection; `replay=0` skips the backlog, `daemon=0` drops the daemon frames. A remote caller, or one passing `daemon=0`, must pass `sessions` (400 otherwise) |
| `GET` | `/v1/daemon` | **local** — `{content}`: the `daemon.log` lines of the last 28 days. `?from=` / `?to=` (`yyyy-MM-dd-HH-mm`, local time) narrow the window and `?keyword=` keeps only lines containing it (case-insensitive); a malformed time returns 400 |
| `GET` | `/v1/system/update` | **local** — added in v1.0.11. `{version, latest, update_available}`; `latest` is resolved by following the GitHub `releases/latest/download/x` redirect (502 with the running `version` when that lookup fails) |
| `POST` | `/v1/system/update` | **local** — added in v1.0.11. Opens a terminal window running `agen update` and returns `202 {status:"opened"}`; the update itself runs in that terminal, not in the daemon. macOS uses Terminal.app, WSL launches the Windows terminal, Linux tries `xdg-terminal-exec`, `x-terminal-emulator`, `gnome-terminal`, `konsole`, `xfce4-terminal`, `kitty`, `alacritty`, `wezterm`, `xterm` in that order. 501 when no terminal is reachable |
| `GET` | `/v1/mcp/tools` | List the tools registered from connected MCP servers (`mcp__*`) |
### `POST /v1/send` semantics
The body is `{content, session_id?, sse?, model?, skill?, work_dir?, system_prompt?, exclude_tools?, persist?}`; `content` is required. `work_dir` defaults to the home directory and must resolve to an existing directory (400 otherwise). A request that arrives while the session is still running is appended to that run as a steer message instead of starting a new one, and returns `{session_id, steer:true}` at once. Otherwise `sse:true` streams the run's events; without it the reply arrives when the run ends as `{text, model, usage, session_id}`, or `{session_id, canceled:true}` when it was canceled (500 with `error` when it failed).
| `persist` | `session_id` | Result |
|---|---|---|
| `false` (default) | empty | Creates `temp-`, removed after 30 min without changes |
| `true` | empty | Creates `chat-`, retained |
| any | provided | Uses the supplied `session_id` (`persist` is ignored) |
The `chat` flag was removed in v1.0.11 and the `http-` prefix with it: a persisted HTTP session now gets the same `chat-` prefix the dashboard uses, so one prefix covers every persisted non-CLI session. Existing `http-` sessions keep working when named explicitly through `session_id`.
```bash
curl --fail-with-body -sS \
-H 'Content-Type: application/json' \
-d '{"content":"List the available tools","persist":false}' \
http://127.0.0.1:17989/v1/send
```
`/v1/chat/completions` is stateless: include prior messages in every request when continuity is needed. `reasoning_effort` accepts `none` `low` `medium` `high` `xhigh` `max` (plus the aliases `minimal` `extra` `ultra`); an omitted or unrecognized value falls back to the level the model selector picks for the request (`medium` when it picks none). `model` defaults to `auto`.
By default a completion runs with none of Agenvoy's own tools: only the `tools` the client sends are offered. With the header `X-Agenvoy-Agent: 1` (or `true` / `yes`) it runs as an Agenvoy agent instead — Agenvoy's system prompt plus every tool except the TUI-only ones. Tool confirmations are skipped on this endpoint either way.
---
Source: https://agenvoy.com/docs/rest-api-models.md
# Model Endpoints
Endpoints for registered models, routing, tiers, quota and audio models.
| Method | Path | Description |
|---|---|---|
| `GET` | `/v1/models` | List registered models (OpenAI `{object:"list", data:[...]}` shape, `auto` included), plus the same names as a plain `models` array |
| `GET` | `/v1/models/*id` | Read one registered model; an unknown id returns 404 with the OpenAI `model_not_found` error |
| `POST` `DELETE` | `/v1/models` · `/v1/models/*name` | **local** — `POST` `{prefix, models}` sets the whole model list of one provider prefix (a provider id from `GET /v1/providers` or a custom compatible instance): listed IDs not yet registered are added, registered `prefix@` models missing from the list are removed, and the reply is `{ok, added, removed}`; `auto` is reserved (400). `DELETE /v1/models/@` removes one model (404 when unknown), moves `dispatcher` / `summary` to the first remaining model when they pointed at it, and resets sessions pinned to it to `auto` |
| `GET` `POST` | `/v1/model` | **local** — model routing in one object: `dispatcher`, `dispatcher_beta`, `summary`, `image`, `stt`, `tts`, plus `image_options`, `image_providers` and `audio_providers` on read. The fields hold two different kinds of value. `dispatcher`, `summary`, `stt` and `tts` name a registered model (`prefix@model`) — `stt` and `tts` are drawn from `GET /v1/model/audio`, not from the session model registry. `image` names an image model as `provider@model` since v1.0.13 — `image_options` is now probed from each credentialed provider (cached for 15 minutes since v1.0.18) (`openai`, `grok`, `grok-oauth`, `gemini`, and `openrouter` since v1.0.17) with an image-only model filter, so the choice is a model rather than an endpoint. `codex` is the exception and still appears as the bare provider name, and a bare provider name stored from an earlier version is still accepted. `image_providers` and `audio_providers` remain the full provider catalogs (`image_providers` is `openai`, `codex`, `grok`, `grok-oauth`, `gemini`, `openrouter`; `audio_providers` is `openai`, `gemini`, `openrouter`). `dispatcher_beta` is a boolean added in v1.0.18 that routes through the TypeSafe dispatcher instead of `dispatcher`; setting it to `true` without a `TYPESAFE_API_KEY` in the keychain returns 400 with `missing_key`. The global `auto_reasoning` toggle was removed in v1.1.0 — set a session's `reasoning` to `auto` instead, and the model selector picks the level per request from the kind of work. `POST` is a partial update — a field left out (or `null`) is untouched, `""` clears it, and `off` is an alias of `""` for `image`. An unregistered model, an unknown provider, or a provider with no credentials is rejected and nothing is written. `GET` first resets a stored `image` choice whose provider is no longer reachable to off |
| `GET` | `/v1/model/quota` | **local** — added in v1.1.0. `?model=@` returns `{quota}`: that model's provider quota as a display string (`"42%"` for `codex`, `grok-oauth`, `copilot`, `claude-code` — since v1.1.3 one percentage per reported window joined by `/`, e.g. `"37%/80%"`; `"$12.34"` for `openrouter`, `deepseek`), or `""` when the provider reports no quota, has no credential, or the read fails. Read live (15 s ceiling, no cache). The dashboard fetches it after a completion instead, since quota was removed from completion events and channel footers in v1.1.0 |
| `GET` | `/v1/model/audio` | **local** — `stt_options` and `tts_options`: the speech-to-text and text-to-speech models reachable with the credentials stored right now, fetched from OpenAI, Gemini and OpenRouter (added in v1.0.17) and returned as `provider@model`; since v1.0.18 each provider's answer is cached for 15 minutes, with the same invalidation as `/v1/provider/:provider/models`. These are the only values `POST /v1/model` accepts for `stt` / `tts` |
| `GET` `POST` | `/v1/model/priority` | **local** — fallback order of the registered models: after the selected model fails, the others are tried top to bottom. Since v1.1.0 `pass`-tier models are skipped (v1.0.20 through v1.0.27 still tried them at their place in the order). `GET` returns `models` (names in order), `tiers` (the `model_tag` map, model → tier) and `tier_options` (`{tier, detail}` rows, `""` first for no tier). `POST` `{models}` moves the listed names to the front in that order and keeps the rest after them; an unknown name returns 400 |
| `POST` | `/v1/model/tier` | **local** — `{model, tier}`. Sets one registered model's tier: `S`, `A`, `B`, `C`, or `pass` (since v1.1.0 never picked by auto routing or subagents, even when the request names it, and skipped by fallback; it still runs when set as a session's own model); `""` clears it. An unregistered model or unknown tier returns 400 |
---
Source: https://agenvoy.com/docs/rest-api-sessions.md
# Session Endpoints
Endpoints for reading, updating and deleting sessions and their pending and finished tasks.
| Method | Path | Description |
|---|---|---|
| `GET` | `/v1/sessions` | `{sessions:[{id, self_id, name, state, model, count}]}`, most recently active first. `?filter=` is `all` (default), `active` (a run is live), `permanent` (everything but `temp-`) or `temporary` (`temp-` only) |
| `GET` | `/v1/usage` | **local** — 24 h / 7 d / 28 d total token usage across sessions. Since v1.0.11 each per-model row also carries `elapsed_ms` (summed provider round-trip time) and `output_tps` (output tokens per second, computed only over the rows that recorded an elapsed time). Since v1.1.0 the `input` of `claude` and `claude-code` rows includes cache writes (`write`), which those providers report separately; since v1.0.26 each usage record also stores the IDs of the tool calls the response made (not returned here) |
| `POST` | `/v1/session` | **local** — create a session; `{prefix}` defaults to `cli-` |
| `GET` `POST` `DELETE` | `/v1/session/:id` | **local** — one session's full state in one call: `id`, `self_id`, `name`, `role`, `state`, `model`, `reasoning`, `levels`, `count`. `role` (the persona body) replaced `rule` in v1.1.0; `rule` is still returned with the same value and still accepted on `POST` for older clients. `levels` starts with `auto` since v1.1.0 — the model selector then picks the reasoning level per request from the kind of work — and `auto_reasoning` was removed with the global toggle. `POST` is a partial update — `self_id` / `name` / `role` / `model` / `reasoning` are all optional and a field left out (or `null`) is untouched; `model: ""` resets to `auto`, `reasoning` must be one of `levels`. A duplicate `self_id` returns 409. `DELETE` removes the session directory, history, state, and vectors. `GET` also takes `?chat=1` to append the action log under `chat` and `?usage=1` to append per-model token usage under `usage`; both are off by default because the log can be large. Since v1.0.12 the `chat` payload is trimmed: for a task that already reached `done` / `canceled` / `error`, its `tool_*` and `thinking` lines and every assistant line but the last are dropped, so a finished task contributes its outcome rather than its whole trace |
| `POST` | `/v1/session/:id/memory` | **local** — one memory operation, picked by `action`: `summary` rebuilds the rolling summary and returns `count`; `compact` drops older messages and returns `removed`; `reset` clears the conversation and returns `removed`, and requires `mode` — `summary` keeps the rolling summary, `all` wipes it too |
| `POST` | `/v1/session/:id/followup` | **local** — added in v1.1.2. Generates follow-up suggestions from the last 4 history messages (each clipped to 512 characters) and returns `{title, suggests}`: up to 3 suggestions of at most 32 characters, and a `title` of at most 32 characters only while the session still needs one (it is also saved as the session name). An empty session returns `{"title":"","suggests":[]}`; the call is capped at 30 s. Before v1.1.2 the suggestions arrived as an `EventSuggest` event at the end of the `/v1/send` stream, which no longer emits it — the dashboard now calls this endpoint after a run finishes |
| `POST` | `/v1/session/:id/cancel/:task_hash` | Cancel one running task. Returns `{ok, cancelled:true}` when that task hash is running in this process; otherwise (or for `current`) it records a canceled event on the session stream and returns `{ok, cancelled:false, stale:true}`. Since v1.2.0 the task hash is created and registered when the run starts, so a cancel lands from the run's first event; TUI runs execute in the daemon too and can be canceled here |
| `POST` | `/v1/session/:id/confirm/:confirm_hash` | Resolve an outstanding tool confirmation: `{approve, remember?, allow_turn?, abort?, reason?, password?}`. 410 when the confirmation was already resolved or expired. Approving a confirmation that lists `restricted` paths must come from loopback (403 otherwise) and passes the OS password check with `password` (401 on failure); the check is skipped while the sudo ticket is still cached, which the confirm event reports as `password_cached` |
`GET` / `POST` / `DELETE /v1/session/:id`, `memory` and `followup` return 404 when the session directory does not exist.
`POST /v1/session/:id/event` was removed; the TUI now exchanges events with the daemon over its Unix socket (`~/.config/agenvoy/daemon.sock`). Removed in v1.2.0.
## Pending and completed tasks
| Method | Path | Description |
|---|---|---|
| `GET` | `/v1/session/:id/task` | List resumable pending (`ask_user` / confirm) tasks as `{pending:[{task_hash, objective, has_questions}]}`. Tasks whose run is still live are excluded — a run refreshes `action::` in ToriiDB every 55 s with a 60 s TTL (v0.35.3), so a task left behind by a closed window or a killed process reappears here within a minute |
| `GET` | `/v1/session/:id/task/:task_hash/questions` | `{questions:[{question, detail?, options?, multi_select?, secret?}]}` of one pending task; 410 when the task was already resolved elsewhere |
| `POST` | `/v1/session/:id/task/:task_hash/resume` | `{answers?}`. Answers a pending task and resumes it, waiting up to 60 min for the run: `{session_id, text}`, or `{session_id, canceled:true}`; 410 when the task was already resolved elsewhere |
| `DELETE` | `/v1/session/:id/task/:task_hash` | Discard a pending task without answering; 404 when there is no such pending task |
| `GET` | `/v1/session/:id/task/history` | **local** — completed tasks of this session, newest first: `{task_hash, end_at, objective, model, reasoning}` per row. `?keyword=` filters on the objective and the recorded action text |
| `GET` | `/v1/session/:id/task/:task_hash/history` | **local** — the full action record of one completed task, returned as a JSON string under `content`. 404 when that hash has no record |
---
Source: https://agenvoy.com/docs/rest-api-channels.md
# Channel Endpoints
Endpoints for the Telegram and Discord channels.
| Method | Path | Description |
|---|---|---|
| `GET` | `/v1/channel` | **local** — every channel read in one object: `telegram` and `discord` each carry `{enabled, username, has_token}`, and `admin` carries `{channel, authorized, chats:[{value,type,id,name}]}`. `chats` comes from the `.telegram` / `.discord` auth files (tg first, then dc) and each `value` can be posted back as-is; `authorized` says whether the current relay target is still on that list (a hand-typed ID reads `false`) |
| `POST` | `/v1/channel/telegram` · `/v1/channel/discord` | **local** — `{action:"enable"\|"disable", token?}`. Enable requires `token` (400 otherwise); disable deletes the token from the keychain and clears the flag. Enable stores the token and flips the config flag only; the token check the TUI performs (`verifyTelegram` starts a bot client and reads its username) is intentionally skipped, since the daemon's config-file watcher already reconnects the bot and fills in its username |
| `GET` | `/v1/channel/:channel/chats` | **local** — chats that finished verification for `telegram` / `discord`. Only meaningful while the bot runs, so ask for it after the channel reports `enabled` |
| `DELETE` | `/v1/channel/:channel/chat` | **local** — `{id}`. Drops one chat from that auth file; the chat has to verify again before the bot answers it, and the reply is `{ok, removed}`. 404 when the id is not on the list or no chat has been authorized yet |
| `POST` | `/v1/channel/admin` | **local** — `{value:"tg@"\|"dc@"\|""}`. Sets where new-chat verification codes are relayed; an empty string clears it. `value` is required (omitting it returns 400 so an empty body cannot silently clear the setting). Only the format is validated — an ID that is not authorized makes the relay log a warning and keep the code log-only |
---
Source: https://agenvoy.com/docs/rest-api-credentials.md
# File and Credential Endpoints
Endpoints for files, stored keys and live voice.
| Method | Path | Description |
|---|---|---|
| `GET` `PUT` | `/v1/file` | **local** — `GET ?path=` returns the file with an `X-Content-SHA256` header (400 for a directory, 404 when missing). `PUT` `{path, content \| content_base64, base_sha256}` writes it and returns `{sha256, path}`; 409 (with `current_sha256`) when the file changed on disk since `base_sha256`, or when it no longer exists. Paths resolve against the home directory and must stay inside it; sensitive paths return 403 |
| `GET` | `/v1/file/open` | **local** — `?path=`. Opens a file with the OS default handler and returns `{result}`. Same home-only and sensitive-path rules as `/v1/file`; URLs are not accepted here |
| `GET` | `/v1/file/locate` | **local** — find candidate paths for a bare file name and return `{paths}`. `name` must be a bare name; `size` is required unless `dir=1` (400 otherwise); `child` (repeatable, directories only) and `mtime` (ms, within 1 s) narrow the match. Searches `Desktop`, `Downloads`, `Documents`, `Pictures`, `Movies`, `Music`, `Library/Mobile Documents`, then the rest of home, and stops at the first of those roots with a match (8 s budget) |
| `GET` | `/v1/workdir` | **local** — resolve and validate a work directory (`?path=`, empty means home), returning `{path}`; 400 when it cannot be resolved, does not exist, or is not a directory |
| `DELETE` | `/v1/key` | **local** — `?key=`. Deletes a single credential from the keychain and from the key list in `config.json`. The `GET` that returned a stored value was removed in v1.0.23 |
| `GET` `POST` | `/v1/keys` | **local** — `GET` returns `keys`, the names registered in `config.json` (never the values). `POST` `{key, value}` (both required) writes the keychain and registers the name |
| `GET` | `/v1/voice/live` | **local** — added in v1.1.1. WebSocket upgrade that relays the dashboard's voice mode to Gemini Live (`gemini-3.8-live`) with `GEMINI_API_KEY` from the keychain. The daemon sends the setup itself — voice system prompt in the configured reply language, the `send_to_chat` / `read_history` / `read_task` function declarations, input and output transcription — then passes frames both ways. Closes with code `4401` when the key is missing and `1011` when the upstream dial or setup fails |
---
Source: https://agenvoy.com/docs/rest-api-providers.md
# Provider Endpoints
Endpoints for provider catalogs, logins, credentials, quota and model lists.
| Method | Path | Description |
|---|---|---|
| `GET` | `/v1/providers` | **local** — list providers and their available auth methods. Each row also carries `logged_in`, true only for the OAuth providers (`codex`, `copilot`, `grok-oauth`) that currently hold a token and for `claude-code` while it is enabled, `hidden` (v1.1.0; true for `claude-code` unless the daemon was started with `agen --enable-claude-code`, which needs a stopped daemon — `agen stop` first), and (since v1.0.22) `console`, the sorted page names that `/v1/provider/:provider/console` serves for it (`key` / `billing` for API-key providers, `plan` for subscriptions, empty for `compat`) |
| `GET` | `/v1/providers/quota` | **local** — remaining quota for `codex`, `grok-oauth`, `copilot`, `claude-code` (since v1.1.0), `openrouter` and `deepseek`, returned under `quota` keyed by provider. `ollama-cloud` was removed from quota tracking; check its usage on ollama.com (Removed in v1.2.0). Since v1.1.3 each entry carries `remaining`, holding only the fields that provider reports: `five_hour`, `week` and `total` (percent left in each window) or `balance` (credit in USD); before v1.1.3 it was a single `kind` (`percent` / `balance`) plus `value`. Fetched in parallel with a 10 s ceiling. Renamed from `/v1/providers/usage` in v0.35.3. Successful reads are cached in ToriiDB for 3 minutes and come back flagged `cached:true`; `?refresh=1` drops the cache and re-reads, and saving a key or finishing an OAuth login drops that provider's entry on its own. Providers without a credential come back with `error` instead of `remaining` and are never cached |
| `POST` | `/v1/provider/:provider/key` | **local** — `{api_key, account_id?, gateway_id?}`. Stores `_API_KEY` in the keychain; `account_id` is required for `cloudflare`, and a provider without an API-key method returns 400. `compat` takes `{name, url, api_key?}` instead and adds or updates a custom compatible instance (key stored as `COMPAT__API_KEY`) |
| `GET` | `/v1/provider/:provider/oauth` | **local** — SSE login flow. Only `copilot` is a device-code flow (`{url, user_code}`); `codex` and `grok-oauth` emit `{url}` for a browser login; `claude-code` only checks that the `claude` CLI is installed. The stream ends with `{done, ok, error?}`; a provider without OAuth returns 400. An existing token is left in place while the login runs, so an abandoned or failed re-login no longer logs the provider out |
| `DELETE` | `/v1/provider/:provider/oauth` | **local** — clear a stored provider login (`codex`, `copilot`, `grok-oauth`). The token keys belong to the OAuth libraries, so this goes through their own `ClearToken` rather than `DELETE /v1/key` |
| `GET` | `/v1/provider/:provider/models` | **local** — `{models, windows}`: the text models this provider's own API lists for the stored credential; a custom compatible instance is probed at its `/models` endpoint. 400 when the provider has no model listing, 502 when the fetch fails. Since v1.0.15 the response also carries `windows`, a `{model: {in, out}}` map of context-window sizes for the models the window table knows; a model with no entry is simply absent from the map. Since v1.0.18 the list is cached in ToriiDB for 15 minutes per provider and dropped as soon as that provider's key is set, its OAuth completes, or its login is cleared |
| `GET` | `/v1/provider/:provider/console` | **local** — added in v1.0.22. `302` redirect to the provider's console page; `?page=` is `key`, `billing` or `plan`, defaulting to `plan` for subscription providers and `key` otherwise. 404 when the provider has no such page. The TUI (`o` in `/model add`) and the dashboard's Console / Plans links go through it, so the URLs live in one table (`providerConsole` in `handler/providers.go`) |
---
Source: https://agenvoy.com/docs/rest-api-mcp.md
# MCP Endpoints
Endpoints for adding, logging in to and managing MCP servers and their tools.
| Method | Path | Description |
|---|---|---|
| `GET` `POST` | `/v1/mcp` | **local** — list / add MCP servers. `POST` `{name, server}` writes one entry to the MCP config; `server` needs a `command` or a `url` (400 otherwise). The `GET` also returns `oauth: {name: bool}` for the HTTP servers, saying which ones already hold a token |
| `POST` | `/v1/mcp/remove` | **local** — `{name}`. Removes an MCP server; 404 when the name is not configured |
| `GET` | `/v1/mcp/status` | **local** — connection status per server |
| `POST` | `/v1/mcp/reconnect` | **local** — reconnect all MCP clients and re-register tools |
| `GET` | `/v1/mcp/oauth?name=X` | **local** — SSE OAuth login for one HTTP MCP server, mirroring the provider flow: emits `{"url":...}` for the browser, then `{"done":true,"ok":...}` (plus `reconnect_error` when the post-login reconnect fails). Times out after 10 minutes, or when the client disconnects. 404 when the server is not configured or is not an HTTP/SSE server |
| `POST` | `/v1/mcp/oauth/callback` | **local** — `{name, url}`. Hands the redirect URL back when the browser cannot reach the daemon's loopback listener on `localhost:17988`; the code is parsed out of the URL's query. 400 if no login is waiting for that server |
| `POST` | `/v1/mcp/oauth/client` | **local** — `{name, client_id, client_secret?, redirect_uri?}`. Stores a pre-registered OAuth client for servers that reject dynamic registration; `redirect_uri` defaults to `http://localhost:17988/callback` and must match the provider console exactly. Clears any existing token first and returns `{ok, redirect_uri}`; 404 under the same conditions as the login |
| `DELETE` | `/v1/mcp/oauth` | **local** — `{name}`. Clears both the stored token and the client registration for that server |
---
Source: https://agenvoy.com/docs/rest-api-skills.md
# Role, Skill and Allowlist Endpoints
Endpoints for roles, skills and the skill and tool allowlists.
## Roles & skills
| Method | Path | Description |
|---|---|---|
| `GET` | `/v1/roles` | **local** — list session-prompt roles stored as `.md` files under `prompts/` (`name`, `size`, `updated_at`), returned under `roles` and, for older clients, the same list again under `rules` |
| `GET` | `/v1/role/*name` | **local** — read one role |
| `POST` `PATCH` `DELETE` | `/v1/role` | **local** — create / update (with optional `rename`) / delete a role. `POST` and `PATCH` take `{name, content}`; a name already taken returns 409. `DELETE` takes `?name=` (or `{name}`); 404 when the role does not exist |
| `GET` | `/v1/skills` | **local** — list installed skills as `{name, description?}`, TUI-only skills excluded; since v1.2.0 skills from sources disabled through `/v1/config/skill_source` are not scanned and do not appear |
| `GET` | `/v1/skill/*name` | **local** — read one installed skill: `name`, `description`, `path`, `source`, `content`, `deletable`, plus `files` — every UTF-8 file under the skill's `scripts/` `references/` `assets/` directories as `{path, content}`, sorted by path, dotfiles skipped and anything over 1 MiB omitted (v1.0.2) |
| `DELETE` | `/v1/skill` | **local** — `?name=`. Moves one installed skill to `~/.config/agenvoy/skills/.Trash` and returns `{ok, trashed}`. Only skills under `~/.config/agenvoy/skills/` can be removed — not `.system` or `.system_design`, and not other sources (403) |
Rules were renamed to roles in v1.1.0. Five routes remain as aliases of the role handlers — `GET /v1/rules`, `GET /v1/rule/*name`, and `POST` / `PATCH` / `DELETE /v1/rule` — so older clients keep working.
Operator notes were removed in v1.0.23: `/v1/notes`, `/v1/note/*name` and `/v1/note` (formerly `/v1/knowledge*`) no longer exist. Put standing instructions in a role or a skill instead.
## Allowlists
| Method | Path | Description |
|---|---|---|
| `GET` `POST` | `/v1/allowlist` | **local** — both allowlists in one object, `skill` and `tool`. `GET` reads `?scope=global\|project` (with `?work_dir=` required for `project`) for the skill block and `?prefix=` to narrow the tool block. `POST` takes `{skill: {name, scope?, work_dir?}}` to toggle one skill and/or `{tool: {prefix, entries}}` to replace just that prefix's auto-approve entries (the same call the TUI's `/mcp` → permission makes), so unrelated rules survive; every entry must start with `prefix`, and `prefix*` collapses the rest. A block left out is untouched |
---
Source: https://agenvoy.com/docs/rest-api-automation.md
# Automation Endpoints
Endpoints for scheduled runs.
Cron entries and one-off tasks are one surface. The separate `/v1/cron*` and `/v1/task*` endpoints were replaced by `/v1/schedule` in v0.33.6, with `type=cron|task` selecting between them.
| Method | Path | Description |
|---|---|---|
| `GET` | `/v1/schedule` | **local** — list cron entries and one-off tasks as one `schedules` array, each tagged `type=cron\|task`; `?type=` narrows to one |
| `GET` | `/v1/schedule/*skill` | **local** — read a scheduler skill (404 when missing): `name` (also echoed as `skill`) plus `body`, which since v1.0.2 is the **raw** SKILL.md including its frontmatter rather than the parsed-off body. `files` carries the skill's `scripts/` `references/` `assets/` contents on the same terms as `GET /v1/skill/*name`. The `description` field was removed |
| `POST` `PATCH` | `/v1/schedule` | **local** — `{type, name, content, expressions, session_id?}`. Creates (`POST`, 409 when the name is taken) or updates (`PATCH`, 404 when missing) a scheduler skill and rebind its whole entry set to `type=cron\|task`; switching type drops the entries the skill held under the other one. `description` was removed in v1.0.2. A `content` that already opens with its own `---` frontmatter is written verbatim; otherwise the server composes a minimal `---\nname: \n---` header. `name` is 1-64 characters of `A-Z a-z 0-9 _ -`. `expressions` (at least one) are cron expressions for `cron` and future times for `task` (`+`, `YYYY-MM-DD HH:MM`, RFC 3339, or `HH:MM` today). `session_id` defaults to the session the skill already runs in, else a new `chat-` session. Returns `{type, name, session_id}` |
| `DELETE` | `/v1/schedule` | **local** — `{skill, type?}`. Deletes a skill's entries (`type` narrows to one, omitted removes both) and trashes the skill when nothing else binds it; returns `{ok, removed, trashed}`, 404 when nothing was removed |
| `POST` | `/v1/schedule/run` | **local** — `{session_id, skill}`, both required. Fires a schedule now and returns `202` `{ok, started:true}` without waiting for the run |
---
Source: https://agenvoy.com/docs/rest-api-config.md
# Configuration Endpoints
Endpoints for global settings.
| Method | Path | Description |
|---|---|---|
| `GET` `POST` | `/v1/config/startup` | **local** — launch-on-login. `GET` returns `enabled` (the recorded setting) and `installed` (whether the launchd agent or systemd user unit exists). `POST` `{enable}` (required) writes or removes the launchd agent (macOS) or systemd user unit (Linux) and returns `{ok, enabled, installed, detail}`; it never starts or stops the running daemon, and takes effect at the next login |
| `GET` `POST` | `/v1/config/system` | **local** — reply language. `GET` returns `reply_lang` (`auto` by default) and `languages` (`{code, label}` options, `auto` first). `POST` `{reply_lang}` (required) canonicalizes the code, rejects values over 64 bytes or containing a line break, and writes `reply_lang` to `config.json` |
| `GET` | `/v1/config/reply_lang` | **local** — added in v1.0.11, read-only (a `POST` to this target returns 404). Returns the configured `reply_lang`, its resolved `name`, and the `directive` sentence injected into the system prompt — empty for `auto`; since v1.2.0 the system prompt then falls back to a built-in default (the user's language, else English; Chinese as Traditional Chinese (Taiwan)) |
| `GET` `POST` | `/v1/config/output_dir` | **local** — output directory. `GET` returns `output_dir` (the raw setting) and `resolved` (the directory in use; `~/Downloads` when unset and that folder exists). `POST` `{output_dir}` (required) expands `~`, creates the directory, and writes it to `config.json`; `""` restores the default |
| `GET` `POST` | `/v1/config/official_guide` | **local** — added in v1.2.0. Per-model official guide injection. `GET` returns `{enabled}`; `POST` `{enable}` (required) writes `official_guide_disabled` to `config.json` and returns `{ok, enabled}` |
| `GET` `POST` | `/v1/config/skill_source` | **local** — added in v1.2.0. Skill source toggle. `GET` returns `sources` (`claude`, `codex`, `copilot`, `opencode`, `agents`; the `openai` source was removed in v1.2.1) and `disabled`; `POST` `{disabled}` (required, every name must be one of `sources`, otherwise 400) writes `skill_source_disabled` to `config.json`, rescans skills, and returns `{ok, disabled}` |
Any other `:target` returns 404.
---
Source: https://agenvoy.com/docs/rest-api-inspection.md
# Inspection Endpoints
Endpoints for usage, logs and other read-only inspection.
| Method | Path | Description |
|---|---|---|
| `GET` | `/v1/torii/error` | **local** — read the tool-error memory store. `keyword` alone decides the mode: without it the store is listed (`?tool=` narrows that listing to one tool, `?limit=` defaults to 50); with it the records are searched (`?tool=` narrows the search too, `?limit=` defaults to 16). Both return `{records}`. Before v1.0.1 a bare `?tool=` fell through to the search path and came back empty |
| `PATCH` | `/v1/torii/error` | **local** — `{id, action}`, both required. Replaces the recorded action of one error-memory record and returns it under `record`; 404 when the id does not exist |
The `/v1/toriidb` HTTP gate was removed in v0.35.3. Since ToriiDB v0.6.2 every process talks to the store over ToriiDB's own socket, so tool cache, chat vectors, error memory and pending liveness are shared without an HTTP hop.
---
Source: https://agenvoy.com/docs/config-files.md
# Configuration
## File layout
```
~/.config/agenvoy/
├── config.json Main config — models, priority, tiers, routing roles, channel flags, reply language, output dir, limits, policy overrides
├── daemon.log Daemon log
├── runtime.uid Server-mode singleton record
├── daemon.sock Unix socket (0600) the TUI uses to run agents and MCP operations in the daemon (v1.2.0)
├── mcp.json Global MCP servers
├── tasks.json One-shot scheduled tasks
├── crons.json Recurring cron tasks
├── allow_skill Global skill always-allow list (one name per line)
├── allow_tool Global tool auto-approve list (one pattern per line, `prefix*` allowed)
├── .telegram / .discord Authorized chat IDs (one per line, written after OTP success)
├── .store/
│ ├── history.db SQLite — session, message_meta, messages + FTS5, action_history, file_history, usage
│ ├── db_0 ... db_3 ToriiDB — tool and quota cache, chat vectors, error memory, online markers
│ └── temp/ Removed files moved aside with a timestamp suffix
├── prompts/ Session prompt roles (.md, surfaced through /v1/roles)
├── skills/ Skill storage (git-versioned)
│ ├── .system/ Bundled skills
│ ├── .system_design/ Optional skills installed with /skill
│ ├── scheduler/ Scheduler skill dirs (-/SKILL.md)
│ └── .Trash/ Removed skills (recoverable from .Trash)
├── tools/ Tool storage (git-versioned)
│ ├── api/ API tools (single JSON each)
│ ├── script/ Script tools (tool.json + script.py)
│ ├── .system/ Bundled tools
│ └── .extension/{api,script}/ Extension-installed tools
├── vendor/ Web dashboard's third-party assets, downloaded at daemon startup and served at /vendor/*
├── download/ Inbound chat attachments and downloaded files (.Trash/ inside); default output dir when ~/Downloads is missing
└── sessions/
├── .Trash/ Removed sessions
└── /
├── history.json Recent conversation, delta-appended
├── summary.json Rolling summary
├── .summary_cursor Incremental summary cursor
├── action.log Tool call audit trail — [ts][window_hash][kind][task_hash] body
├── .cmd_history Per-session TUI input history
├── .claude_code/ claude-code session id and resume state, one file per model + system prompt hash (v1.1.3; claude_code_.json since v1.1.1)
└── pending/ Pending ask_user / confirm metadata, one JSON per task hash
/.config/agenvoy/ Project-scoped tools and skills
├── tools/{api,script}/
└── skills/
/.agenvoy/allow_skill Project-scoped skill always-allow list (union with global)
/.agenvoy/allow_list Project-scoped tool allow list
```
## Where things deliberately do not live
- **Provider API keys** — never in `config.json`; always in keychain
- **MCP credentials** — use `${VAR}` placeholders in `mcp.json` and keep values in env vars
- **Secrets captured by `store_secret`** — keychain only; never in LLM context, history, `action.log`, or tool args
- **Session config and persona** — in the SQLite `session` table under `.store/`, not per-session JSON files
- **Session history** — `history.json` for the hot window, mirrored into ToriiDB and SQLite under `.store/`
- **Operator notes** — removed in v1.0.23 along with `find_note`, `/note` and `/v1/note*`; neither the SQLite `note` table nor `~/.config/agenvoy/knowledge/` is read any more. Use a role or a skill for standing instructions
- **Tool call results** — in-memory dedupe per run plus a 30-minute ToriiDB cache for `fetch_page`, `search_web`, and `http_request` `GET` calls; nothing else persists across restarts
---
Source: https://agenvoy.com/docs/config-files-json.md
# config.json
The model, routing, and channel keys in `~/.config/agenvoy/config.json`. Runtime limits are in [config.json Limits](/docs/config-files-json-limits); policy keys are in [config.json Policies](/docs/config-files-json-policy).
Model, routing, and channel settings:
| Key | Description |
|---|---|
| `models` | Registered models as `"@"` strings; the order is the fallback priority (`pass`-tier models are skipped since v1.1.0) |
| `model_tag` | `{model: tier}` with tiers `S` `A` `B` `C` `pass`; written as `{}` when missing. Since v1.1.0 a `pass` model is never picked by auto routing, subagents or fallback, even when a request names it; only a session whose own model is set to it uses it |
| `dispatcher_model` / `summary_model` | Dispatcher and summary roles |
| `dispatcher_beta` | Added in v1.0.18. `true` routes through the TypeSafe dispatcher instead of `dispatcher_model`; requires `TYPESAFE_API_KEY` in the keychain |
| `image_generator` / `stt_model` / `tts_model` | Image provider endpoint and audio models |
| `compats` | Custom OpenAI-compatible endpoints, `[{provider, url}]` |
| `keys` | Names of credentials stored in the keychain (never the values) |
| `telegram_enabled` / `discord_enabled` | Channel flags; `telegram_username` / `discord_username` are filled in by the daemon |
| `admin_channel` | Verification-code relay target (set from `/config` → **Admin Channel** in the TUI since v1.1.0) |
| `reply_lang` | Reply language, default `"auto"` (match the user). Accepts a code from `configs/jsons/reply_lang.json` (`en`, `zh-TW`, `zh-HK`, `zh-CN`, `ja`, `ko`, `es`, `fr`, `de`, `pt`, `it`, `ru`, `vi`, `th`, `id`, `ar`) or any language name |
| `output_dir` | Where files made for the user land when no location is named; default `""` means `~/Downloads`, or `~/.config/agenvoy/download` when that folder does not exist. `~` is expanded; an unusable path falls back to the default |
| `official_guide_disabled` | Added in v1.2.0. `true` stops injecting the per-model official guide (`## Model Guide`) into the system prompt; set from `/config` → **Official guide**, the dashboard's System tab, or `POST /v1/config/official_guide` |
| `skill_source_disabled` | Added in v1.2.0. External skill sources to skip when scanning, any of `claude`, `codex`, `copilot`, `opencode`, `agents` (`openai`: Removed in v1.2.1); set from `/skill` → `source`, the dashboard's Skills page, or `POST /v1/config/skill_source` |
`reply_lang` and `output_dir` are set from `/config` in the TUI or `GET` / `POST` `/v1/config/system` and `/v1/config/output_dir`; `/config` also toggles **Startup on login** (`/v1/config/startup`). The separate startup, reply-language, and output-dir TUI commands were removed; use `/config`. The daemon watches `config.json` and reloads the agent registry and Telegram / Discord on every write.
`auto_reasoning` (added in v1.0.18) was removed in v1.1.0: reasoning is now a per-session setting, and a session whose `reasoning` is `auto` gets its level from the model selector per request. An `auto_reasoning` entry left in `config.json` is ignored.
---
Source: https://agenvoy.com/docs/config-files-json-limits.md
# config.json Limits
The runtime limits `config.json` can raise, and the package defaults it cannot change.
Missing `reply_lang`, `output_dir`, and `model_tag` are filled with defaults and written back on startup. Since v1.1.2 missing limit fields are no longer filled in, and a limit can only raise its default: the effective value is `max(config value, default)`, so a smaller value is ignored.
| Key | Default | Description |
|---|---:|---|
| `limits.max_tool_iterations` | `256` | Maximum tool iterations per run (`128` before v1.1.2) |
| `limits.agent_send_timeout_seconds` | `600` | Model-request timeout |
| `limits.max_history_messages` | `16` | Recent history messages retained (`24` before v1.1.2) |
| `limits.max_history_bytes` | `4194304` | History-size ceiling. Changed from `5242880` in v1.0.12, where it became `MAX_DOCUMENT_BYTES * 4` — four times the 1 MiB single-document ceiling |
Package defaults that are **not** read from `config.json` (since v1.1.2 all of them live in `configs/configs.go`):
| Constant | Default | Description |
|---|---:|---|
| `MAX_SESSION_TASKS` | `3` | Concurrent tasks per session; further tasks queue rather than fail (`NumCPU × 4` before v1.1.2) |
| `MAX_CONCURRENT_SUBAGENTS` | `3` | Subagent legs running at once; the rest queue |
| `MAX_SUBAGENT_TIMEOUT_MIN` | `30` | Subagent timeout in minutes |
| `MAX_RESUME_WAIT_MIN` | `60` | How long a pending resume waits for answers |
| `MAX_CONCURRENT_TOOLS` | `5` | Concurrent tool calls within one model turn; the rest queue (v1.0.20) |
The daemon port is **not** configurable: `17989` is a package constant, and a `limits.port` entry is ignored.
There are **no environment variables** for runtime limits — env-based overrides were removed; `config.json` is the only knob.
---
Source: https://agenvoy.com/docs/config-files-json-policy.md
# config.json Policies
The policy keys in `config.json` that widen or narrow what tools may touch, and the keys that are no longer read.
Policy keys in `config.json`. The first two merge with values embedded in the binary (user entries add to, never replace, the defaults); the rest exist only as user config:
| Key | Embedded source | Purpose |
|---|---|---|
| `sensitive_path` | `configs/jsons/sensitive_path.json` | Credential and key-material paths — reachable only after a password-backed per-session grant. Buckets: `dirs`, `files`, `prefixes`, `extensions` |
| `read_only_command` | `configs/jsons/read_only_command.json` | `run_command` calls that skip the confirm gate (`git status`, `ls`, `cat`, ...). Since v1.0.25 an entry is matched against the binary plus at most its first two arguments (`git config --get`, `docker compose ps`), a call with `network: true` or a sensitive-path argument never matches, and the dedicated read-only command tool is gone |
| `denied_command` | — | Binaries `run_command` refuses outright, inside `sh -c` too |
| `denied_path` | — | Paths permanently off limits for reads and writes; no prompt can approve them. Entries must be absolute or start with `~/`; the filesystem root is refused |
| `net_white_list` | — | Hosts exempt from the `http_request` SSRF guard |
Three keys are no longer read: `sensitive_map` (renamed `sensitive_path`), `white_list` (removed — commands run unless listed in `denied_command`), and `path_white_list` (removed — paths outside `$HOME` are approved per session). Up to v1.1.1 their presence logged a startup warning; v1.1.2 dropped the warning, so they are now ignored silently.
v0.35.0 dropped the last pre-v0.28.9 compatibility paths: `limits.max_skill_iterations` (superseded by `limits.max_tool_iterations`) and `planner_model` (superseded by `dispatcher_model`; up to v1.1.1 it was deleted from `config.json` on the next save, since v1.1.2 it is simply left in place) are no longer honoured, and the legacy `api_tools/` / `script_tools/` directories are no longer read — tools live under `tools/api/` and `tools/script/`.
---
Source: https://agenvoy.com/docs/config-files-project.md
# Project Configs
The JSON rule files and prompt templates that ship inside the repository.
```
configs/
├── jsons/
│ ├── sensitive_path.json Credential / key-material paths
│ ├── exclude_list.json Listing/walking exclude paths
│ ├── read_only_command.json Commands that skip confirmation
│ ├── guardrail_rules.json Guardrail rules rendered into the system prompt
│ ├── refusal_messages.json Refusal message per reply language
│ ├── local_compat.json Built-in local endpoints (Ollama, llama.cpp)
│ ├── reply_lang.json Reply-language codes and labels
│ └── tui_tools.json TUI-only tools and skills
└── prompts/ System prompt templates, reasoning guides, channel prompts
```
Provider and model catalogs no longer ship in the repository — model lists are fetched live from each provider's API.
---
Source: https://agenvoy.com/docs/config-integrations.md
# Integration Config
## MCP config
One file: `~/.config/agenvoy/mcp.json`. The session-scoped layer was removed. Manage servers with `/mcp` in the TUI, the web dashboard, or the `/v1/mcp*` endpoints. See MCP Client for the full schema and `${VAR}` expansion behavior.
## Provider config
There are no static provider catalogs — model lists come from each provider's API, or a local endpoint's `GET /models`, when a model is added. Registered models live in `config.json` under `models` (their order is the fallback priority), tiers under `model_tag`, and custom endpoint URLs under `compats`; credentials never do, they live in the OS keychain under service `agenvoy`. A write to `config.json` reloads the agent registry without a restart. The `claude-code` provider (since v1.1.0) stores no credential — it runs the local `claude` CLI — and is enabled only when Agenvoy is started with `agen --enable-claude-code` (daemon must be stopped).
### Compat provider URL storage split
`compat` provider URLs use a **two-storage** model:
| What | Where | Why |
|---|---|---|
| URL (e.g. `http://host:8000/v1`) | `~/.config/agenvoy/config.json` `compats[]` — `{"provider": "", "url": "..."}` | Non-secret, user-editable |
| API key (`COMPAT__API_KEY`) | OS keychain | Secret |
URL convention follows Zed: the user enters the URL up to `/v1` (e.g. `http://host:8000/v1`) and the router appends `/chat/completions`. Local Ollama (`http://localhost:11434/v1`) and llama.cpp (`http://localhost:8080/v1`) are built in through `configs/jsons/local_compat.json` and need no `compats` entry. The URL is read from config, **not** the keychain — there is no `COMPAT__URL` keychain key (intentionally removed: a historical bug had the TUI writing to config while the runtime read the keychain, always falling back to localhost).
## KuraDB
There is no `kuradb_enabled` flag and no `/v1/kuradb` endpoint. KuraDB is registered as an ordinary MCP server — one entry in `mcp.json` — so its tools arrive under `mcp__kura__*` and vanish when it is unregistered or disconnected. The `/kuradb` TUI wizard was removed in v0.35.0: run `kuradb.sh` yourself (it needs a real TTY for `sudo`), then register through `/mcp`. See KuraDB RAG for the full picture.
| Key | Location |
|---|---|
| `kura` server entry | `~/.config/agenvoy/mcp.json` — `{"command": "kura", "args": ["mcp"]}` |
| `OPENAI_API_KEY` | keychain (`agenvoy` service) — shared with semantic search |
| Binary | `kura` on `PATH` (`/usr/local/bin/kura`, from `kuradb.sh`) |
| KuraDB's own config / data | `~/.config/kuradb/`, managed entirely by KuraDB |
---
Source: https://agenvoy.com/docs/config-integrations-channels.md
# Telegram and Discord Setup
Where the Telegram and Discord bot settings, tokens and authorized chats are stored.
| Key | Location |
|---|---|
| `telegram_enabled` / `discord_enabled` | `config.json` (bot usernames are filled into `telegram_username` / `discord_username`) |
| `TELEGRAM_TOKEN` / `DISCORD_TOKEN` | keychain (`agenvoy` service) |
| Authorized chat IDs | `~/.config/agenvoy/.telegram` and `~/.config/agenvoy/.discord` (one ID per line, written after 6-digit OTP verification succeeds) |
| Verification-code relay | `admin_channel` in `config.json` — `/config` → `Admin Channel` in the TUI (moved there from `/channel` → `admin` in v1.1.0; `/channel` now holds only the Telegram and Discord tabs), or `POST /v1/channel/admin`, picks an authorized chat or a raw `tg@` / `dc@` target. The `/admin-channel` command was removed |
| Inbound voice messages | Transcribed with whichever model `stt_model` in `config.json` names (`/model` → `stt` in the TUI). With none selected the bot answers that no speech-to-text model is set instead of transcribing |
Both channels can also be enabled or disabled through `POST /v1/channel/telegram` and `POST /v1/channel/discord`, which store the token and flip the flag; the daemon's config watcher reconnects the bot and fills in its username. Platform formatting rules are embedded in the channel system prompts — there is no `format_chatbot` tool. Since v1.2.0 those prompts also tell the model that TUI commands and shortcuts (`/config`, `/model`, ...) do not exist in a chat, so it never suggests one; `/` still works there.
Both bots connect outbound from the daemon, so the host needs no inbound port, public hostname, or reverse proxy — a bot token is the whole setup. That is also the bar for adding another platform: one is considered only when it improves on this, not merely to widen the list.
Voice **output** was removed from both channels. `[SEND_VOICE:]` no longer exists and replies are never synthesized into a chat; speech synthesis is the `generate_audio` tool, which writes a file to the output directory and is gated on `tts_model`. Since v1.0.13 that file defaults to opus, and a `.ogg` / `.oga` / `.opus` attachment sent to Telegram goes out as a voice note instead of a document — Discord still receives it as a file.
---
Source: https://agenvoy.com/docs/built-in-tools.md
# Built-in Tools
27 tools always register, plus `pkg_manage` on Linux and macOS; four more appear only when their prerequisite exists (`generate_audio`, `generate_image`, `list_chatbot`, `send_to_chatbot`) — 32 built-in tools in total. `find_note` was removed in v1.0.23 together with operator notes; standing instructions for a session belong in its role (`/role`). Tools that cover several related actions take a `mode` argument rather than splitting into separate names — `edit_file` replaces the former `write_file` / `patch_file` / `remove_file` / `restore_file`, `find_files` replaces `list_files` / `glob_files` / `search_files`, and so on.
Since v1.2.0 a run no longer sends every schema up front. For every provider except `claude-code`, the tools array holds only `find_tools`, `run_tool` and `reasoning_guide` (plus any client tools; `reasoning_guide` joined the fixed set in v1.2.1); every other tool — built-in, `script_*`, `api_*`, `ext_*`, `mcp__*` — is listed by name in the per-turn context (`Tools (call through run_tool): ...`). The model fetches a schema with `find_tools(query="select:NAME[,NAME]")`, which returns it as tool output, then calls the tool as `run_tool(name, args)`; the runtime unpacks that into the inner tool, so confirmation, boundary checks and argument validation still apply by the real name. A direct call to a listed tool is refused with a pointer to `run_tool`, a `run_tool` call made before the schema was fetched returns the schema and asks for a re-call, and a name that is not registered never runs. Keeping the tools array fixed keeps the provider prompt cache hitting. The `AlwaysLoad` flag was removed; use `find_tools` to load a schema (Removed in v1.2.0). The `claude-code` provider (enabled with `agen --enable-claude-code`) gets every tool except `run_tool` and calls them by name, but receives only the names: a call made before `find_tools` fetched that tool's schema is not run and returns the schema instead.
## Conditional registration
These are excluded from a run while their prerequisite is missing; the LLM does not see them then.
| Tool | Concurrent | Description |
|---|---|---|
| `generate_audio` | | Speaks text with the configured text-to-speech model and writes the file to the output directory (`output_dir`, else `~/Downloads`, else `~/.config/agenvoy/download/`); returns the saved path, not the audio data. Since v1.0.13 the request asks the provider for `opus` and the extension follows the returned MIME type (`.ogg` for opus/ogg, `.mp3`, `.aac`, `.flac`, `.wav` as a fallback) — a `.ogg` / `.oga` / `.opus` result attached to a Telegram chat is delivered as a voice note rather than a document. `voice` picks a provider voice (`alloy` for OpenAI, `Kore` for Gemini), blank uses the provider default. 5 min cap. *(excluded while no text-to-speech model is selected — set it with `/model tts` or `POST /v1/model` `{tts}`)* |
| `generate_image` | | Text to image, written to disk; also handles edits when a reference image is given. Returns the saved path, not the image data. 15 min cap. *(excluded while the image generator is off — set it with `/model` or `POST /v1/model` `{image}`. Since v1.0.13 the setting names an image model as `provider@model`, probed from each credentialed provider with an image-only filter and cached for 15 minutes since v1.0.18; `codex` remains a bare provider name, and a bare provider name saved by an earlier version still resolves)* |
| `list_chatbot` | | List authorized chats for the specified platform (`platform=telegram` or `platform=discord`). *(registered at daemon start only when `telegram_enabled` / `discord_enabled` is set and the bot token is in the keychain, and excluded from a run while neither platform qualifies)* |
| `send_to_chatbot` | | Send a formatted message to an authorized chat by `target_id`. Telegram: HTML + transient client. Discord: markdown + transient client. Platform formatting rules live in the channel system prompts, not in a tool |
## Dynamic tool groups
Beyond the built-in registry, four prefixes resolve at call time:
| Prefix | Source |
|---|---|
| `script_*` | Generated script tools under the script-tool directory — `script.py` executed in the sandbox |
| `api_*` | Generated API tools — a single JSON descriptor per endpoint |
| `ext_*` | Extension tools, API or script backed |
| `mcp__*` | Tools exposed by connected MCP servers |
RAG reaches the agent this way rather than as a built-in: KuraDB is registered as an ordinary MCP server, so its `list_rag` / `search_rag` tools arrive under the `mcp__` prefix and disappear entirely when it is unregistered or disconnected.
---
Source: https://agenvoy.com/docs/built-in-tools-files.md
# File Tools
Tools that find, read, edit, open and track files.
| Tool | Description |
|---|---|
| `find_files` | Locate files (`queries: [{dir, pattern, file_pattern, recursive}]`). `mode=list` returns a directory's entries (alphabetical), `mode=glob` matches paths against a filename pattern and, since v1.0.23, returns them most recently modified first, `mode=search` greps file contents by RE2 regex. Mode is inferred when omitted: `pattern` + `file_pattern` → search, `pattern` alone → glob, neither → list. Glob patterns must carry a literal — all-wildcard (`**/*`) is rejected. Matches merge and deduplicate across queries. `mode=list` and `mode=glob` are capped at 128 KiB in total and the tail reports how many entries were dropped. Since v1.0.11 `mode=search` is paged instead of truncated: `output=files` (the default) returns one `{path, count, lines}` row per matching file — since v1.0.23 `lines` carries the row numbers of its first five matches, ready for `read_files` `around` — `output=content` returns the matching lines with their line numbers, `multiline=true` (v1.0.23) matches the regex against whole files so `.` crosses newlines and reports every row a match spans, `offset` / `limit` (default 256) walk the pages, and `context` adds surrounding lines — marked `context: true` and not counted toward `offset` / `limit`. A single match line is cut at 512 bytes. Every page ends with `[files a-b of N ...]` / `[lines a-b of N ...]` carrying the next `offset`, or `last page`; the queries and `output` must stay identical between pages |
| `read_files` | Batched read of one or more files (`files: [{path, offset, limit, around, context}]`); text, PDF, DOCX, PPTX, CSV/TSV, image, or audio/video — a media file comes back as a verbatim transcript through the configured speech-to-text model, so there is no separate transcription tool. Reads up to 2048 lines by default (1 MiB cap for documents, 10 MiB for images); `offset`/`limit` page through larger files — page for PDF, slide for PPTX, row for CSV. Since v1.0.23 `around` takes 1-based rows of a plain-text file and reads `context` lines (default 20, `0` = the rows alone) on each side, merging overlapping windows and marking skipped text with a `...` line (`offset`/`limit` are then ignored), and the same path may appear more than once in `files`, its results joined in order. Since v1.0.11 a truncated text read ends with `[lines a-b of N; call again with offset=... for more]`, so the continuation offset never has to be guessed. Text lines arrive as `"\t"`; the number is added by the reader and is not in the file, so it must be stripped before a line is reused as an `edit_file` anchor. Must be called before `edit_file` changes an existing file: since v1.0.23 the edit is refused unless the file was read in this run and its modification time is unchanged since that read. **Sensitive file guard**: SSH keys, `.pem`, `.key`, `.env` always require confirmation regardless of sudo or allowlist |
| `edit_file` | Every change to a file on disk. `mode=write` creates a first version or deliberately replaces one wholesale; `mode=patch` edits regions via a `targets` array — each target is `{old_string, new_string}` plus optional `replace_all`, and every anchor is located against the original file content before anything is applied. Two targets covering the same region, or a target whose `old_string` occurs inside an earlier target's `new_string`, reject the whole call with nothing written. So does an `old_string` that matches more than once without `replace_all`, a patch that would leave a non-empty file empty, the history placeholder pasted back as content, or a file over 1 MiB. An empty `new_string` deletes; an insert is expressed by repeating `old_string` at the start of `new_string`. The line-anchored `row` / `insert_string` form was removed — row numbers counted the earlier targets in the same call, which are not yet on disk; `mode=remove` moves the file aside, still restorable; `mode=restore` puts a recorded version back by `version` id, or undoes a whole task via `task_id` (`current` = the task running now). Mode is inferred from `content` → write and `targets` → patch; remove and restore are never inferred. Since v1.0.23 `write` over an existing file and every `patch` pass a read-freshness guard: the file must have been read with `read_files` in this run and not changed on disk since (by the user, a formatter or another command), otherwise nothing is written and the error asks for a fresh read; a successful write counts as a fresh read for the next edit. A `patch` anchor that only differs from the file in curly vs straight quotes still matches, and the replacement keeps the file's quote style. The tool description sends a file made for the user with no location asked for to the output directory |
| `file_history` | Recorded versions of every file the tools changed — when each changed, what the task was after, and what the content was. `mode=list` returns versions newest-first, filterable by `path`, `task_id`, `from`/`to` local time, and `limit` (capped at 24); `mode=read` diffs the newest recorded version of each path against what is on disk now. This is the snapshot layer `edit_file(mode=restore)` restores from |
| `open_file` | Open a file or URL with the OS default handler (play a video, view an image, open a PDF viewer; since v1.2.1 also a link or a system settings pane such as `x-apple.systempreferences:...`). A URL always asks the user to confirm. Replaces `run_command` `open`/`xdg-open`, which the sandbox cannot reach. 10 s cap |
---
Source: https://agenvoy.com/docs/built-in-tools-extension.md
# Tool and Skill Tools
Tools that find, create, test and run other tools and skills.
| Tool | Description |
|---|---|
| `find_tools` | The tool registry. `mode=search` matches by keywords (all must match) or `select:,` for exact activation, and returns the matching schemas as its tool output (the tools array is never edited mid-run); `mode=list` returns names plus one-line descriptions with no schema injection — `mcp=true` narrows to MCP-exposed tools, and a system flag also lists the internal bookkeeping tools. Mode is inferred: `query` → search, otherwise list. A capability that looks missing is checked here before anything is built |
| `run_tool` | Added in v1.2.0. Calls a tool from the per-turn `Tools` list by name: `run_tool(name, args)`, with `args` matching that tool's schema (`{}` when it takes none). Every tool other than `find_tools` goes through it for providers other than `claude-code`; the runtime unpacks the call into the inner tool before confirmation and validation, and its own handler never runs. A name not fetched with `find_tools` first returns its schema for a re-call; an excluded or unregistered name is rejected. Not sent to `claude-code` |
| `edit_tool` | The tool definitions themselves. `mode=write` creates or overwrites, `mode=patch` fixes an exact string after `test_tool` failed, `mode=remove` trashes the directory (recoverable from `.Trash`). `tag` picks the file: `json` = `tool.json` (schema), `script` = `script.py` (runtime), `api` = `.json` (API tool). Names are snake_case with no `script_` prefix — the runtime adds it |
| `test_tool` | Runs a script tool's `script.py` in the sandbox with JSON on stdin and returns what it printed. The verification step after every `edit_tool` write or patch; on failure the loop is `edit_tool(mode=patch)` → test again |
| `run_skill` | Load a named skill's reference material into the current turn; the result is `skill: `, the skill directory, and the resolved `SKILL.md` body. It is reference, not a script to execute line by line. Since v1.1.0 a skill matched or named at the start of a run arrives the same way: the runtime records a `run_skill` call whose tool result carries the skill content, rather than adding a separate system message |
| `edit_skill` | The files under `~/.config/agenvoy/skills/`. `mode=write` creates or rewrites a file at a relative path (`my-skill/SKILL.md`), `mode=patch` replaces an exact string, `mode=remove` trashes a whole skill directory by single-segment name (recoverable from `.Trash`) |
---
Source: https://agenvoy.com/docs/built-in-tools-web.md
# Web and Network Tools
Tools for web search, page fetching, HTTP requests and downloads.
| Tool | Concurrent | Description |
|---|---|---|
| `search_web` | ✓ | Live web lookup returning DuckDuckGo results and Google News headlines together as `{"web":[...],"news":[...]}`. `source` picks `all` / `web` / `news`; `time_range` applies a lookback window to both (news caps at 7 d); `edition` sets the news locale (`TW:zh-Hant`, `US:en`); `cdp=true` forces a browser fetch (auto-enabled on HTTP 202); a refresh flag skips the result cache. 90 s cap |
| `fetch_page` | ✓ | Fetch one web page as `markdown`, `html` or `json` (readability + 4xx/5xx skip cache via ToriiDB). Cookies from the persistent Chrome profile are sent by default so login-required sites work transparently; headless is attempted first and retried visibly on failure, and `headless: false` opens a visible window right away when a display exists (before v1.1.4 it forced headless-only instead). `save=true` writes to a local file instead of returning the content, and a same-domain link option supports recursive document research. 90 s cap |
| `http_request` | ✓ | Raw HTTP request (GET/POST/PUT/DELETE/PATCH), returns status + headers + body. `content_type` selects `json` / `form` / `multipart`; multipart takes `{"fields":{...},"files":[{"name","path","content_type"}]}` with absolute paths read as binary. Timeout up to 300 s. Built-in SSRF guard (DNS-resolved against loopback / private / link-local); bypass specific hosts via `net_white_list`. An endpoint you will call again belongs in an `api_*` tool built with `edit_tool` |
| `download_file` | ✓ | Download a binary file to local disk (tar.gz, images, archives, installers). Absolute `path` used as-is, relative joined under `~/.config/agenvoy/download/`, parent dir auto-created. Timeout up to 600 s. For JSON/HTML use `http_request` or `fetch_page(save=true)` |
| `html_template` | ✓ | Added in v1.0.17. Gallery of worked HTML page examples served from `https://view.agenvoy.com`: `mode=list` returns every template's `name`, `category`, `title`, `desc`; `mode=read` returns one template's full HTML by `name`. Used while building an HTML deliverable after `reasoning_guide(topics=["html_render"])`, which then saves it with `write_result(format="html")`. Replaces the former `http_request` / `fetch_page` calls to the same gallery. Any other web page → `fetch_page`. 30 s cap |
---
Source: https://agenvoy.com/docs/built-in-tools-execution.md
# Execution Tools
Tools that run commands and manage packages.
| Tool | Description |
|---|---|
| `run_command` | Execute a binary with argv (argv-only schema, sandbox-wrapped via `go-pkg/sandbox`). A plain command with no shell metacharacter (`\|`, `&&`, `>`, `*`, `~`) is called directly, never wrapped; pipes, redirects and globbing need an explicit `['sh','-c','...']`, whose script is parsed and validated command-by-command — every binary must be a **bare command name**, and anything on `denied_command` is rejected outright. `['cd','']` is special-cased and mutates `Executor.WorkDir` after verifying the path; `rm` is routed to the trash rather than deleting, and is rejected inside `sh -c`; on macOS `open` is rejected with a pointer to `open_file`, since the sandbox has no route to the app-launch service. Each call is capped at 30 min (`RUN_COMMAND_TIMEOUT`). Since v1.0.25 the sandbox keeps networking off by default: a command that has to reach a host (`git clone` / `fetch` / `pull` / `push`, `npm` / `pip install`, `go mod download`, `curl`) sets `network: true`, and such a call never counts as read-only, so it always goes through the confirm gate. Installing or removing a system package, Homebrew included, goes to `pkg_manage`, not `run_command`. Commands on the read-only allowlist (`configs/jsons/read_only_command.json`, 410 entries, merged with `read_only_command` from `config.json`) skip the confirm gate: since v1.0.25 the binary plus up to two following arguments are matched (`git status`, `docker ps`, `kubectl get`, `ls`, ...), and a match is cancelled when any argument is a sensitive path. The dedicated read-only command tool added in v1.0.23 (`run_command_readonly`, which ran in parallel) was removed in v1.0.25, and the short-lived `run_script` never shipped in a release; use `run_command`, which always runs serially. To write outside `$HOME` the call carries `write_paths`, which are bound in only after a password-backed approval. `sudo` is rejected outright (bare or inside `sh -c`) with guidance to drop it and declare `write_paths`, which raises that sudo confirm instead. Since v1.0.11 a file watcher is rejected before it starts: `--watch` / `--watch=...`, a `chokidar` binary, or an `npm` / `pnpm` / `yarn` / `bun` script that resolves to one — the package script is parsed out of `package.json` and followed up to 4 levels (5 before v1.1.2), through `sh -c` too, and since v1.1.2 a script already visited is not followed again — because `run_command` waits for the process to exit and a watcher never does. The error names the one-shot build to run instead |
| `pkg_manage` | Drive the system package manager **outside** the sandbox — Linux: apt / dnf / yum / pacman / apk; macOS: Homebrew — so the root operations `bwrap` cannot grant, and Homebrew's own nested sandbox, still work. `action` is `install` / `remove` / `update` / `upgrade` / `search` / `info`; `package` is a bare name — no flags, no version pin, no second package — and is required for everything except `update` and `upgrade`. Registered on Linux and macOS (macOS since v1.2.0). On Linux, `install` / `remove` / `update` / `upgrade` run through `sudo -n` unless the daemon is root and raise the password-backed confirmation; on macOS `brew` runs without `sudo` and the call takes only the ordinary confirmation. `run_command` cannot substitute: sudo is powerless inside `bwrap`. Language runtimes (node / python) → `run_command` with mise, fnm or uv; language-level packages (pip / npm / cargo) → `run_command` |
---
Source: https://agenvoy.com/docs/built-in-tools-coordination.md
# Coordination Tools
Tools for subagents, schedules, checklists, questions to the user and final results.
| Tool | Description |
|---|---|
| `subagents` | `mode=invoke` runs a subtask in its own session (in-process, no HTTP); `mode=list` returns the reusable named sessions and their roles. Written to stand alone — the leg sees none of the parent conversation. **At most 3 legs run concurrently**; a 4th queues while its own timeout runs, so wide fan-outs dispatch in batches of 3. Results carry a `[subagent · · session= · usage: in=X out=Y cached=Z write=W]` header that rolls up into parent-session usage. Since v1.0.22 the leg's report body no longer comes back inline: it is written to `~/.config/agenvoy/download/temp-.md` and the result is that header plus the file path, which the caller reads with `read_files` before relaying or synthesizing. `report_name` is a short kebab-case name (job + entity, e.g. `nvda-news`); a taken name gets a random suffix, and a blank one uses a random name. `self_id` reuses an existing non-temp session verbatim — it replaced `name` / `session_id` in v0.35.0 and is now required for `mode=list`, which resolves one delegated name instead of dumping every session. Since v1.1.0 `model` is required on every `mode=invoke` call — the dispatcher fallback for a blank model was removed, and `model: "auto"` (hotfix after v1.1.0) hands the choice to the dispatcher, which picks from the task as for a normal request; when `self_id` resolves to a session pinned to its own model that model runs instead, and a session left on `auto` runs the given one. Timeout `MAX_SUBAGENT_TIMEOUT_MIN` (30 min). Since v1.0.21 each leg has one job — collect, analyze, compare, review, transform or code — mapped to a work kind; since v1.1.0 the leg's tier order runs one tier lower than the dispatcher's (collect → `fetch` C>B>A>S, transform → `chat` C>B>A>S, analyze / compare / review → `work` B>A>C>S, code or high-precision → `code` A>B>C>S). The former `reason` job was split into analyze, compare and code. `model` is checked against the live registry, so models added after startup work; an unregistered or `pass`-tier name is rejected with the list of usable models. `reasoning` defaults to `auto` (the leg's session setting, else the dispatcher's pick); a value that is not a reasoning level is rejected. Legs only gather: a leg never gets `subagents`, `edit_file`, `edit_tool`, `test_tool`, `edit_skill`, `write_result`, `html_template`, any `generate_*` tool, `open_file`, `schedules`, `store_secret`, `pkg_manage`, or the TUI-only tools, and code comes back as text for the main agent to apply; `exclude_tools` adds to that set. Full dispatch protocol and model tiers → `reasoning_guide(topics=["subagent_dispatch"])` |
| `schedules` | Scheduled runs bound to a scheduler skill. `mode=list` shows what this session has queued, `mode=patch` moves an entry to a new time, `mode=remove` cancels it and trashes its skill, `mode=write` is the internal binding step of the `scheduler-skill-creator` flow. `target` is `task` (one-shot), `cron` (5-field expression), or `all` (list only). Task time formats: `+5m` / `+1h30m` (relative), `15:04` (today), `2006-01-02 15:04` (local), or RFC3339. `skill_name` carries a generated hash suffix — hand-made values always fail |
| `write_todo` | Live task checklist the user watches in real time. The entire ordered list is resent on every call (state is replaced, not merged); exactly one step stays `in_progress`. While a plan runs the step set is fixed — only status advances. The checklist is stored per task hash, so concurrent tasks in one session keep separate lists. When the last step completes and no `report-*.md` or `report-*.html` was written in that task, the result tells the model to call `write_result` for a long-form deliverable. Since v1.0.26 it carries the `Concurrent` flag, so it runs alongside the other fan-out calls of the same round. A checklist opens whenever the answer takes more than one round of tool calls, and for any analysis or research request however short the plan looks; a running Skill whose steps take more than one round opens one built from its own steps before the first step — dropping the ones the request does not reach; single-pass Skills get none (v1.1.1 used a three-step threshold) |
| `ask_user` | Free-text / single-select / multi-select / masked-secret prompts. Each question carries an optional multi-line `hint` for supporting detail. Since v1.0.23 the TUI (`cli-` origin) asks inline: the question appears in place and the same run continues with the answer. Since v1.2.0 the daemon runs the agent and sends the question over its Unix socket only to the TUI window that started the run. Every other origin (web, Telegram, Discord, API, scheduler) saves a pending snapshot — tool results clamped to 32 KiB and arguments to 4 KiB each (4 KiB / 1 KiB before v1.1.2) — ends the run, and resumes in a new turn with full context once the user answers. The tool has no timeout. The description tells the model not to ask when an active Skill's `SKILL.md` or data a tool can fetch already settles the question. A credential is never asked for here → `store_secret` |
---
Source: https://agenvoy.com/docs/built-in-tools-state.md
# State and Memory Tools
Tools that search chat, error and action history.
| Tool | Description |
|---|---|
| `chat_history` | This session's own action log. `mode=list` returns recent runs one row each with their `task_id`; `mode=read` opens one or more runs in full — what each was asked for, every tool it called with what came back, and the reply it ended on (`scope=reference` keeps only the expensive payloads, `scope=full` keeps everything); `mode=tool_list` flattens the reusable calls across the last 16 runs so an already-paid-for fetch is found before it is re-run, and `mode=tool` returns one such call's raw output by `task_id` + `name`; `mode=search` searches past messages, `match=semantic` matching meaning in recent messages and `match=keyword` matching text across the full archive (SQLite FTS5, trigram tokenizer), bounded by `time_range`. The records live in the daemon's own store, so file tools never reach them |
| `error_history` | Tool failures kept across sessions. `mode=search` looks up past records by keyword — each carries the fix that worked; `mode=read` fetches one record by its 8-char hash (used when a tool returns `no data: {hash}`); `mode=write` records an error with `tool`, keywords, observed behavior, root cause, what was done, and an `outcome`. Only `outcome=resolved` is stored — `failed` and `abandoned` are accepted but discarded. Search caps at 16 records |
| `reasoning_guide` | Fetch the full rule sets for every topic a turn needs in one call — since v1.0.26 the argument is `topics` (an array, at least one) instead of a single `topic`, and the result joins each guide under a `## ` heading. Topics: `tool_generate`, `tool_error`, `web_search` (named `rag_web` before v1.2.1), `market_analysis`, `targeted_read`, `ask_user`, `subagent_dispatch`, `write_todo`, `html_render`, `office` (rules for creating or modifying `.docx` / `.xlsx` / `.pptx` so Office and iWork apps accept the file). Replaces the former per-topic guide tools. Since v1.2.1 its full schema rides in the tools array next to `find_tools` and `run_tool`, so it is called directly rather than through `run_tool` |
| `write_result` | Renamed from `write_report` in v1.0.17, which added HTML output. Saves one long-form deliverable as `report-YYYYMMDD-HHMMSS.md` or `.html` in the output directory and returns the write receipt with its path — the research / analysis / comparison body the reply summarises instead of reprinting, or a finished HTML page. `content` is the complete file; `format` is `md` (default) or `html`. The output directory is `output_dir` from `config.json` (set with `/config`), else `~/Downloads` when it exists, else `~/.config/agenvoy/download/`. Since v1.2.1 `format=html` is checked before anything is written: the page must mount QuickUI, its inline `