Architecture
OpenVole follows a microkernel architecture — the core provides the agent loop and the plugin contract, nothing else. Everything useful (reasoning, memory, tools, channels, integrations) is a Paw or a Skill.
System Diagram
The Agent Loop
The core of OpenVole — a 6-phase loop that runs per task:
Bootstrap ─┐
▼
Perceive → Compact → Think → Act → Observe → loop| Phase | What happens | Runs |
|---|---|---|
| Bootstrap | Paw hooks load persistent data (memory, session history). VoleNet context injected. | Once per task |
| Perceive | Paw hooks enrich context with dynamic data (time, calendar, unread messages). | Every iteration |
| Compact | Triggered when message count exceeds compactThreshold. Compresses old messages to free context space. | When needed |
| Think | Core builds system prompt, calculates token budget, trims by priority. Brain Paw calls LLM, returns AgentPlan (tool calls + optional response). | Every iteration |
| Act | Core executes tool calls (sequential or parallel). Applies rate limits. Remote tools route via VoleNet. | Every iteration |
| Observe | Paw hooks process results (update memory, log to session, notify channels). Session sync to VoleNet peers. | Every iteration |
The loop exits when:
- The Brain produces a final answer with no tool calls
maxIterationsis reached (resets on successful tool execution)- The task is cancelled
Context Budget
The ContextBudgetManager handles token-aware context management:
- Token estimation — estimates token count for system prompt, tools, messages, session
- Priority trimming — when context exceeds
maxContextTokens, trims in order:- Old tool results (lowest priority — trimmed first)
- Old error messages
- Old assistant/brain messages
- Session history
- Never trimmed — system prompt, first user message, last 2 brain responses
- Response reserve —
responseReservetokens kept for the Brain's output
Tool Horizon
When toolHorizon: true (default), the Brain starts with only core tools visible. It discovers additional tools on demand via discover_tools with an intent query. This prevents context bloat when many paws are loaded — the Brain only sees tools relevant to the current task.
Cost Tracking
The CostTracker records LLM token usage and cost per task:
- Tracks input/output tokens and cost per LLM call
- Supports per-provider pricing (cloud APIs vs local Ollama)
costAlertThresholdwarns when a single task exceeds a USD amount- Configurable via
costTracking:"auto","enabled","disabled"
System Prompt
Core builds the system prompt — Brain Paws are thin API adapters that receive it pre-built via context.systemPrompt.
The prompt is assembled from:
- BRAIN.md — custom system prompt (overrides default if present)
- Identity files — SOUL.md, USER.md, AGENT.md
- Skills — available skill summaries
- Tools — tool descriptions and parameters
- Memory — agent memory context
- VoleNet — instance name, role, peer list with tools and brain status
- Date/time/platform — dynamic context
Paws
Paws are subprocess-isolated plugins. They connect OpenVole to the outside world — APIs, databases, browsers, messaging platforms. Each Paw runs in its own Node.js process with --permission sandbox.
Paws can:
- Register tools with the Tool Registry
- Register lifecycle hooks (
onBootstrap,onPerceive,onCompact,onObserve) - Inject context into the Brain's prompt via
context.metadata - Discover and register tools at runtime (late tool registration)
There are four categories of Paws:
| Category | Examples | Role |
|---|---|---|
| Brain | paw-brain (unified: Ollama, Claude, OpenAI, Gemini, xAI, Claude Code, Antigravity) | LLM reasoning via the Think phase |
| Channel | Telegram, Slack, Discord | Receive messages from external platforms |
| Tool | Browser, Shell, MCP, Database, Email, Scraper, Image | Tools the Brain can call |
| Infrastructure | Memory, Session, Compact, Dashboard | Lifecycle hooks and internal services |
Paw Sandbox
Each Paw process is launched with Node.js --permission flags:
- Filesystem — restricted to
.openvole/paws/<name>/plus explicitly allowed paths - Network — restricted to explicitly allowed domains/IPs
- Child processes — blocked unless
childProcess: true - Environment — only explicitly listed env vars are passed
Optional Docker sandbox available for stronger isolation.
Skills
Skills are behavioral recipes. A Skill is a folder with a SKILL.md file — no build step — optionally with bundled scripts the Brain runs via skill_run_script, sandboxed to the skill's own directory. They tell the Brain how to approach a task by providing instructions, not tools.
Skills activate based on available tools — a skill requiring email_send only loads when an email paw is present. The Brain sees a list of available skills and can load full instructions on demand using the skill_read tool.
Tools
Tools are the runtime abstraction. Every action the Brain can take is a tool — whether it came from a Paw, from the core, from an MCP server, or from a remote VoleNet peer. The Brain doesn't know the difference.
Built-in Core Tools
| Tool | Purpose |
|---|---|
discover_tools | Search available tools by intent (BM25 ranking) |
schedule_task | Create recurring tasks at runtime |
cancel_schedule / list_schedules | Manage schedules |
skill_read | Load skill instructions on demand |
skill_read_reference / skill_list_files | Access skill resources |
skill_run_script | Run a script bundled in a skill, confined to its directory |
heartbeat_read / heartbeat_write | Read/write recurring job definitions |
workspace_write / workspace_read | Read/write agent scratch space |
workspace_list / workspace_delete | List/delete workspace files |
vault_store | Store a secret (write-once, with optional metadata) |
vault_get / vault_list / vault_delete | Retrieve, list, or delete vault entries |
web_fetch | Lightweight URL fetching (GET/POST with headers, body) |
spawn_agent | Spawn a sub-agent with a named profile |
spawn_remote_agent | Delegate a task to a remote VoleNet peer |
list_instances | List connected VoleNet peers |
get_remote_result | Check status of a remote task |
VoleNet
Distributed agent networking. Connects multiple OpenVole instances across machines. Every message is signed and authorized per peer, with hybrid post-quantum signatures (Ed25519 + ML-DSA-65 when the runtime supports it). Sealed envelopes — relayed traffic always, direct traffic with net.encrypt — add post-quantum hybrid confidentiality (X25519 + ML-KEM-768); TLS for the transport is optional on top.
Key capabilities:
- Remote tool execution — tools on remote peers appear in the local registry
- Memory sync — write propagation and remote search across peers
- Session sync — shared conversations across devices
- Brain sharing — brainless workers delegate thinking to a coordinator
- Leader election — automatic failover, heartbeat coordination
- Load balancing — tasks route to the least-loaded peer
See the VoleNet documentation for architecture patterns and setup.
Control Plane & Agents
vole serve runs the control plane — a single web server (the @openvole/dashboard-server package) that manages every agent from one place. This is the primary way to operate OpenVole.
- A agent is an isolated agent — its own
vole.config.json, paws, identity files, and data directory. Each running agent is its own engine subprocess (IPC child), parented to thevole serveprocess (not detached). - All agents under one OpenVole root are recorded in an
agents.jsonregistry. The control plane resolves the root fromVOLE_HOME, else the current directory if it's already a root or empty (see the Dashboard guide). - The control plane aggregates each agent's state and events over IPC and serves one dashboard (Overview / Chat / Apps / Config / Identity) for all of them.
Orchestrator Agents (reverse-RPC)
An agent can be granted orchestrator authority (vole agent create <name> --orchestrator, or vole agent orchestrate <name> on|off), letting its agent manage the sibling agents under the same vole serve. The mechanics:
- The flag is stored in the server's
agents.jsonregistry — outside every agent's sandbox, so an agent can't grant itself authority. - The control plane spawns a flagged agent with
VOLE_ORCHESTRATOR=1; the daemon then registers theagent_*core tools (list, state, submit, task_status, read/write config, read/write identity, restart, start, stop, create) backed by a reverse-RPC client on the same IPC channel the control plane already uses: the child sends{creq:{id,method,params}}, the parent answers{cres:{id,result|error}}. - The parent re-reads the registry and verifies the sender's flag on every request — revoking is instant. Requests targeting other agents reuse the exact per-agent RPC path the dashboard uses, so its guards (demo mode, sandbox-weakening refusal) apply unchanged.
- Hard limits: no self stop/start/restart, no agent removal, and agents created by an orchestrator are never flagged themselves.
- The
vole-orchestrateVoleHub skill (requiresagent_list/agent_submit) supplies the supervisor playbook and only activates in agents that actually have the tools.
Agent Conversations (agent_message)
Orchestration is management; messaging is not. Every agent gets agent_message, whether or not it is an orchestrator — talking to a colleague is not a privilege, and the agent_* management family stays behind the flag.
A message lands in a thread and wakes the agent. The pair's conversation is a session named from each side (
agent:orchestratoron the worker,agent:video-editoron the coordinator), so both keep their own history and no shared registry is needed. Waking is the default: a person's chat message already works that way, and a colleague's word should not sit unread for having arrived over a different channel.A hop budget ends the exchange, not restraint. A reply is itself a message, so if every arrival wakes the receiver, two agents will politely answer each other's answers at one brain call per turn. The count rides with the message and is enforced at
MAX_AGENT_HOPS(4) — enough for ask → clarify → answer → confirm. Past it the message is still delivered; only the waking stops, so the last word is read on the next run rather than lost.A run started by a message answers the agent that sent it, the same way a chat turn answers its chat. One rule (
replyAddressFor) decides where any run reports: its session, else the agent that wrote in, else the project, else the dashboard.The answer reaches the person who asked. When a human's question is what sent an agent to a colleague, the asking run's reply address travels with the message and returns on the answer, and the colleague's reply is delivered straight into that human's chat — badge, live update and all. This does not depend on the agent remembering to relay, because it reliably did not: the reply arrives in a run addressed to the colleague, so relaying was something the model had to think of.
The address is spent on that answer. It used to travel on, which made every later turn count as "on behalf of" the person — so the colleagues' wind-down ("sounds good", "happy to help") was relayed too. One question, one answer; the agents may keep talking, and that conversation stays in their own thread.
Provenance comes from the run, never from the caller. An agent that could name its own relay target could post into a conversation it was never part of. The reply address and hop count are read from the task being executed and attached by the control plane.
Brains that expose tools to a CLI
A brain running with CLAUDE_CODE_EXPOSE_TOOLS=1 calls its tools over the agent's MCP endpoint rather than through the loop. That path is stateless, so the executing side resolves the calling run from the task queue — and only when exactly one task is running. Above taskConcurrency: 1 a stateless call cannot say which run it came from, so no context is attached rather than the wrong one.
Messaging is filed under its own paw name (__agent_chat__), separate from __orchestrate__. The split is what lets anything inspecting the registry tell the two apart — VoleNet withholds both from the mesh by source (CONTROL_PLANE_PAWS), so a tool added to either family later cannot be missed by forgetting to update a name pattern.
Embedded Panels (Apps)
A paw can contribute a UI to the dashboard by declaring a panel in its manifest; the control plane serves the static HTML at /panel/<agent>/<paw>/ and proxies the paw's tools at /panel/<agent>/<paw>/tool/<toolName> — brain-free, directly over IPC. Everything flows through the one control-plane server, so there are no per-paw web servers and no extra ports. See Build an Embedded App for the authoring guide.
Philosophy
If it connects to something, it's a Paw.If it describes behavior, it's a Skill.If the agent calls it, it's a Tool.If it's none of these, it probably doesn't belong in OpenVole.
