Skip to content

Configuration

OpenVole uses a single vole.config.json file at the project root — plain JSON, no imports, no build step.

Full Example

json
{
  "brain": "@openvole/paw-brain",
  "paws": [
    { "name": "@openvole/paw-brain", "allow": { "network": ["*"], "env": ["BRAIN_PROVIDER", "BRAIN_API_KEY", "BRAIN_MODEL", "OLLAMA_HOST", "OLLAMA_MODEL", "OLLAMA_API_KEY"] } },
    { "name": "@openvole/paw-memory", "allow": { "network": ["*"] } },
    { "name": "@openvole/paw-session" },
    { "name": "@openvole/paw-compact" },
    { "name": "@openvole/paw-telegram", "allow": { "network": ["*"], "env": ["TELEGRAM_BOT_TOKEN", "TELEGRAM_ALLOW_FROM"] } },
    { "name": "@openvole/paw-shell", "allow": { "filesystem": ["./"], "env": ["VOLE_SHELL_ALLOWED_DIRS"], "childProcess": true } }
  ],
  "skills": ["clawhub/summarize"],
  "loop": {
    "maxIterations": 25,
    "confirmBeforeAct": false,
    "taskConcurrency": 1,
    "compactThreshold": 50,
    "toolHorizon": true,
    "maxContextTokens": 128000,
    "responseReserve": 4000,
    "costTracking": "auto",
    "costAlertThreshold": 1.00,
    "rateLimits": {
      "llmCallsPerMinute": 30,
      "llmCallsPerHour": 500,
      "toolExecutionsPerTask": 100,
      "tasksPerHour": { "telegram": 20, "cli": 100 }
    }
  },
  "heartbeat": {
    "enabled": true,
    "intervalMinutes": 30,
    "runOnStart": false
  },
  "toolProfiles": {
    "telegram": { "deny": ["shell_exec", "fs_write", "fs_delete"] },
    "heartbeat": { "allow": ["memory_search", "memory_write", "telegram_send", "shell_exec"] }
  },
  "security": {
    "sandboxFilesystem": true,
    "allowedPaths": ["/home/user/projects"],
    "docker": {
      "enabled": false,
      "image": "node:20-slim",
      "memory": "512m",
      "cpus": "1.0",
      "scope": "session",
      "network": "none"
    }
  },
  "agents": {
    "researcher": {
      "role": "Research assistant",
      "instructions": "Search the web and summarize findings. Do not execute code.",
      "allowTools": ["web_fetch", "scrape_page", "memory_write"],
      "maxIterations": 10
    }
  },
  "net": {
    "enabled": true,
    "instanceName": "my-vole",
    "role": "coordinator",
    "port": 9700,
    "peers": [
      { "url": "http://192.168.1.50:9701", "trust": "full", "allowBrain": false }
    ],
    "share": { "tools": true, "memory": true, "session": false },
    "routing": { "shell_*": "worker-1", "db_*": "db-worker" }
  }
}

Config Sections

brain

Which Brain Paw handles the Think phase of the agent loop.

json
{ "brain": "@openvole/paw-brain" }

The unified paw-brain supports all providers — set BRAIN_PROVIDER env var to ollama, openai, anthropic, gemini, or xai. Set BRAIN_PROVIDER=mock for a free, network-free brain (replies via BRAIN_MOCK_REPLY, or scripted tool calls via BRAIN_MOCK_SCRIPT) — handy for testing the dashboard, VoleNet, or the agent loop.


paws

Array of paws to load. Each entry is either a package name string or an object with permissions.

String shorthand — no special permissions:

json
{ "paws": ["@openvole/paw-memory", "@openvole/paw-session"] }

Object form — with explicit sandbox permissions:

json
{
  "paws": [
    {
      "name": "@openvole/paw-brain",
      "allow": {
        "network": ["*"],
        "env": ["BRAIN_PROVIDER", "BRAIN_API_KEY", "BRAIN_MODEL",
                "OLLAMA_HOST", "OLLAMA_MODEL", "OLLAMA_API_KEY",
                "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GEMINI_API_KEY"]
      }
    }
  ]
}

Paw Permission Object (allow)

Each paw runs in a sandboxed subprocess. The allow field controls what the paw can access:

KeyTypeDescriptionExample
networkstring[]Outbound network access. ["*"] for any, or specific domains.["api.openai.com", "api.telegram.org"]
listennumber[]Ports the paw can bind (for servers like dashboard).[3001]
filesystemstring[]Additional filesystem paths beyond .openvole/.["./", "/tmp"]
envstring[]Environment variables passed to the subprocess.["TELEGRAM_BOT_TOKEN"]
childProcessbooleanAllow spawning child processes. Required for shell, browser, MCP paws.true

Hook Configuration

Paw hooks can be configured with ordering and pipeline behavior:

json
{
  "name": "@openvole/paw-memory",
  "hooks": {
    "perceive": { "order": 1, "pipeline": true }
  },
  "allow": { "network": ["*"] }
}

Common Paw Permissions

PawNeedsConfig
paw-brainLLM API access"network": ["*"], env vars for provider
paw-shellSpawn processes"childProcess": true, "filesystem": ["./"]
paw-browserSpawn Chrome"childProcess": true, "network": ["*"]
paw-databaseNative addonsDisable sandbox: "sandboxFilesystem": false
paw-dashboard (deprecated — use vole serve)Bind HTTP port"listen": [3001]
paw-telegramTelegram API"network": ["api.telegram.org"] or ["*"]
paw-memoryEmbedding API"network": ["*"]
paw-compactLLM for compaction"network": ["*"]
paw-mcpSpawn MCP servers"childProcess": true
paw-filesystemRead/write files"filesystem": ["./"]
paw-imageNative addons (sharp)Disable sandbox: "sandboxFilesystem": false

skills

Array of skill names to load. Skills are context-aware prompt templates that activate based on available tools.

json
{ "skills": ["clawhub/summarize", "clawhub/email-triage", "local/my-workflow"] }

Skills from clawhub/ are fetched from the VoleHub registry. Skills from local/ are loaded from .openvole/skills/.


loop

Controls the agent loop — how the Brain thinks, acts, and manages context.

OptionTypeDefaultDescription
maxIterationsnumber10Max loop iterations per task. Resets on successful tool execution.
confirmBeforeActbooleanfalseIf true, ask user confirmation before executing tools.
taskConcurrencynumber1Max tasks running in parallel.
compactThresholdnumber50Message count that triggers compact hooks. 0 to disable.
toolHorizonbooleantrueBrain starts with core tools only, discovers others via discover_tools. Reduces context bloat.
maxContextTokensnumber128000Max context window size in tokens. Core trims messages by priority to fit.
responseReservenumber4000Tokens reserved for the Brain's response output.
costTrackingstring"auto""auto": track for cloud providers. "enabled": always track. "disabled": off.
costAlertThresholdnumberWarn when a single task exceeds this USD amount.
rateLimitsobjectRate limiting (see below).

Rate Limits

json
{
  "loop": {
    "rateLimits": {
      "llmCallsPerMinute": 30,
      "llmCallsPerHour": 500,
      "toolExecutionsPerTask": 100,
      "tasksPerHour": {
        "telegram": 20,
        "cli": 100,
        "heartbeat": 6
      }
    }
  }
}
OptionDescription
llmCallsPerMinuteMax Brain (LLM) calls per minute across all tasks.
llmCallsPerHourMax Brain calls per hour.
toolExecutionsPerTaskMax tool executions within a single task.
tasksPerHourPer-source task rate limits. Keys are source names (cli, telegram, heartbeat, etc.).

Context Budget

The ContextBudgetManager trims messages by priority when the context exceeds maxContextTokens:

  1. Old tool results (lowest priority — trimmed first)
  2. Old error messages
  3. Old assistant/brain messages
  4. Session history

Never trimmed: system prompt, first user message, last 2 brain responses.


heartbeat

Periodic autonomous wake-up. The agent reads HEARTBEAT.md and acts on scheduled jobs without user input.

OptionTypeDefaultDescription
enabledbooleanfalseEnable heartbeat scheduling.
intervalMinutesnumber30Minutes between heartbeat wake-ups.
runOnStartbooleanfalseRun a heartbeat immediately on startup.
json
{ "heartbeat": { "enabled": true, "intervalMinutes": 15, "runOnStart": true } }

Common intervals:

Use CaseIntervalDescription
DevOps monitoring10Health checks, alerts every 10 min
Personal assistant30Email/calendar checks every 30 min
Data monitoring15Watch for changes every 15 min
Content automation360Content cycle every 6 hours
Research aggregator720Daily research report every 12 hours

The heartbeat instructions live in .openvole/HEARTBEAT.md. The agent reads this file each wake-up and decides what actions to take.


toolProfiles

Restrict which tools are available per task source. Useful for limiting what external channels (Telegram, Slack) can trigger.

json
{
  "toolProfiles": {
    "telegram": {
      "deny": ["shell_exec", "fs_write", "fs_delete"]
    },
    "heartbeat": {
      "allow": ["memory_search", "memory_write", "telegram_send", "web_fetch"]
    },
    "cli": {}
  }
}
FieldDescription
allowAllowlist — only these tools can be used. If set, everything else is denied.
denyDenylist — these tools are blocked. Everything else is allowed.

If both allow and deny are set, deny takes precedence. Profile keys match the task source: cli, telegram, slack, heartbeat, api, etc.


security

Controls the subprocess sandbox and isolation.

OptionTypeDefaultDescription
sandboxFilesystembooleantrueEnable Node.js --permission sandbox for paw subprocesses.
allowedPathsstring[][]Extra absolute filesystem paths granted (read + write) to sandboxed paw subprocesses, beyond the space dir / .openvole/.
dockerobjectDocker container sandbox (optional, stronger isolation).
json
{
  "security": {
    "sandboxFilesystem": true,
    "allowedPaths": ["/home/user/data"]
  }
}

Docker Sandbox

Runs paw subprocesses inside Docker containers for stronger isolation.

OptionTypeDefaultDescription
enabledbooleanfalseEnable Docker sandboxing.
imagestring"node:20-slim"Base Docker image.
memorystring"512m"Memory limit per container.
cpusstring"1.0"CPU limit per container.
scopestring"session""session": container per task session. "shared": one container reused.
networkstring"none"Docker network mode: "none", "bridge", or "host".
allowedDomainsstring[]Outbound domains allowed when network: "bridge".
json
{
  "security": {
    "docker": {
      "enabled": true,
      "image": "node:20-slim",
      "memory": "256m",
      "cpus": "0.5",
      "network": "bridge",
      "allowedDomains": ["api.openai.com"]
    }
  }
}

agents

Named agent profiles for sub-agent spawning via the spawn_agent core tool. Each profile defines a restricted execution context.

OptionTypeDefaultDescription
rolestringHuman-readable role description (injected into context).
instructionsstringAdditional instructions for the sub-agent.
allowToolsstring[]Tools this agent can use (allowlist).
denyToolsstring[]Tools this agent cannot use (denylist, takes precedence).
maxIterationsnumber10Max loop iterations for this agent.
json
{
  "agents": {
    "researcher": {
      "role": "Research assistant",
      "instructions": "Search the web and summarize findings. Do not execute shell commands.",
      "allowTools": ["web_fetch", "scrape_page", "memory_write", "memory_search"],
      "maxIterations": 15
    },
    "coder": {
      "role": "Code generator",
      "denyTools": ["telegram_send", "email_send"],
      "maxIterations": 20
    }
  }
}

The Brain can spawn these via spawn_agent({ profile: "researcher", task: "..." }).

TIP

In the control-plane dashboard (vole serve), agent profiles are editable as structured form fields under Config → AGENTS — role, instructions, allowTools, denyTools, and maxIterations — no raw JSON.


net (VoleNet)

Distributed agent networking — connect multiple OpenVole instances across machines.

OptionTypeDefaultDescription
enabledbooleanfalseEnable VoleNet.
instanceNamestring"vole"Human-readable name for this instance.
rolestring"peer""coordinator", "worker", or "peer".
portnumber9700WebSocket/HTTP port for peer communication.
hostnamestringfirst non-internal IPv4Host advertised to peers in the discovery endpoint. Set to your public domain when using TLS so it matches the cert (env override: VOLE_NET_HOSTNAME).
keyPathstring.openvole/net/vole_keyPath to Ed25519 keypair.
peersarray[]Peer connections (see below).
shareobjectWhat to share with peers.
routingobjectTool-to-peer routing rules.
brainSourcestring"local""local", "remote", or a specific peer name.
leaderstring"auto""auto" (lowest instance ID) or a specific instance name.
heartbeatModestring"leader""leader": only leader runs heartbeat. "independent": each instance runs its own.
brainModestring"local""local": handle own tasks. "loadbalance": route to least-loaded brain.
taskOverflowstring"reject""reject": reject when queue full. "forward": forward to least-loaded peer.
maxQueuedTasksnumber10Max queued tasks before overflow triggers.
tlsobject{ cert, key } file paths — enables https/wss transport. Pair with hostname. See Transport encryption.
discoverystring"manual"Peer discovery method: "manual" or "mdns".

Peer Configuration

json
{
  "net": {
    "peers": [
      {
        "url": "http://192.168.1.50:9701",
        "trust": "full",
        "allowBrain": false,
        "allowTools": ["shell_exec"],
        "denyTools": ["vault_read"]
      }
    ]
  }
}
FieldTypeDescription
urlstringPeer endpoint URL.
truststring"full": all access. "tool": specific tools only. "read": memory search only.
allowToolsstring[]Tools this peer can execute on our instance. Glob patterns supported (shell_*). If set, only matching tools are allowed.
denyToolsstring[]Tools this peer cannot use on our instance. Glob patterns supported. Takes precedence over allowTools.
allowBrainbooleanAllow this peer to delegate thinking to our Brain (LLM cost on us). Default: false — off even for trust: "full".

A peer may call our tools at all only if it has explicit trust: "tool" or trust: "full" in peers, or we set share.tools: true (below). Per-peer allowTools/denyTools then refine which tools. By default tools are not exposed to peers.

Sharing

json
{
  "net": {
    "share": {
      "tools": true,
      "memory": true,
      "session": false
    }
  }
}
FieldDescription
toolsAdvertise our local tools to peers and accept remote tool calls from them. This is a blanket grant — a peer with explicit trust: "tool"/"full" in peers can call our tools even without it. Default: tools not exposed.
memoryPropagate memory writes to peers and accept remote memory searches.
sessionSync session transcripts between peers (shared conversation).

Public Join

Let unknown peers self-register over HTTP and join at a restricted guest trust level — for running a public mesh hub that anyone can connect to. Off by default. Self-joined guests are never granted "full" trust.

json
{
  "net": {
    "publicJoin": {
      "enabled": false,
      "trustLevel": "tool",
      "allowBrain": false,
      "maxPeers": 200,
      "ratePerMinute": 5,
      "requireApproval": false
    }
  }
}
FieldTypeDefaultDescription
enabledbooleanfalseAccept HTTP self-join requests from unknown peers.
trustLevelstring"tool"Trust granted to self-joined guests: "read" or "tool". Never "full".
allowBrainbooleanfalseLet guests delegate thinking to our Brain (LLM cost on us).
maxPeersnumber200Max trusted peers before new joins are refused.
ratePerMinutenumber5Join requests allowed per minute per IP.
requireApprovalbooleanfalseQueue joins for manual vole net trust instead of auto-trusting.

Chat Retention

Retention for node-to-node chat sessions (the volenet:<peer> transcripts persisted via paw-session). Unlike brain sessions these have no TTL, so this bounds their growth.

json
{
  "net": {
    "chatRetention": {
      "maxMessages": 1000,
      "maxAgeDays": 90
    }
  }
}
FieldTypeDefaultDescription
maxMessagesnumber1000Max messages kept per peer transcript; the oldest are trimmed on each new message.
maxAgeDaysnumber90Chat sessions idle longer than this are cleared (swept ~every 6h). 0 disables age pruning.

Routing

Route tool calls to specific peers by glob pattern:

json
{
  "net": {
    "routing": {
      "shell_*": "server-worker",
      "db_*": "db-worker",
      "scrape_*": "web-scraper"
    }
  }
}

When multiple peers share the same tool, the Brain can target a specific peer using <peerName>/<toolName> syntax (e.g. us-monitor/shell_exec).

TIP

The whole net section is editable as structured form fields under Config → NET in the control-plane dashboard (vole serve) — including an on/off toggle for enabled, plus peers, share (tools/memory/session), TLS, routing, and the various modes. No raw JSON.

VoleNet Setup

bash
# 1. Generate identity on each instance
vole net init my-instance

# 2. Exchange keys
vole net show-key                    # on instance A
vole net trust "vole-ed25519 ..."    # on instance B (paste A's key)

# 3. Configure peers in vole.config.json (see above)

# 4. Start both instances
vole serve

.openvole Directory Structure

.openvole/
├── paws/
│   ├── paw-memory/          ← memory data
│   │   ├── MEMORY.md
│   │   └── user/, paw/, heartbeat/
│   ├── paw-session/         ← session transcripts
│   │   └── cli:default/, telegram:123/
│   ├── paw-brain/           ← brain paw data
│   │   └── BRAIN.md         ← system prompt (scaffolded on first run)
│   └── paw-mcp/             ← MCP config
│       └── servers.json
├── net/                     ← VoleNet identity (if enabled)
│   ├── vole_key             ← Ed25519 private key
│   ├── vole_key.pub         ← public key
│   └── authorized_voles     ← trusted peer keys
├── workspace/               ← agent scratch space
├── skills/                  ← local and clawhub skills
├── logs/                    ← log files
│   └── vole.log
├── vault.json               ← encrypted key-value store
├── schedules.json           ← persistent cron schedules
├── SOUL.md                  ← agent personality
├── USER.md                  ← user profile
├── AGENT.md                 ← operating rules
└── HEARTBEAT.md             ← recurring job definitions

Each paw gets its own data directory at .openvole/paws/<name>/. The installed npm package stays immutable — all user data lives in the local paw directory.


Workspace

Every space is scaffolded with a .openvole/workspace/ directory — the agent's writable scratch and project area. It's the sanctioned place for anything the agent produces or fetches that isn't memory, config, or a paw's own data:

  • Internal projects and their files (drop source material or media here to work on it)
  • Drafts, notes, and downloaded docs or instructions
  • Generated outputs (reports, renders, exports)

The core workspace_read, workspace_write, workspace_list, and workspace_delete tools operate on this directory and confine every path to it — they run in-core, so the agent can use them without any sandbox grant. Encourage your agent (via AGENT.md or a task prompt) to treat the workspace as its working directory for multi-step projects.

  • Gitignored by default (the scaffolded .gitignore ignores .openvole/), so it's safe for large or throwaway files. Version anything you want to keep elsewhere.
  • Not for secrets — store credentials in the vault, never here.
  • Sandboxed paws don't get workspace access automatically; a paw subprocess that needs it must be granted the path via allow.filesystem (see Security).

Identity Files

Customize agent behavior with markdown files in .openvole/:

FilePurposeUsed By
BRAIN.mdCustom system prompt — overrides the default prompt entirely.Brain Paw
SOUL.mdAgent personality, tone, and identity.System Prompt
USER.mdUser profile, preferences, timezone.System Prompt
AGENT.mdOperating rules and behavioral constraints.System Prompt
HEARTBEAT.mdRecurring job definitions for heartbeat wake-ups.Heartbeat Task

These files are loaded into the system prompt on every iteration. Edit them to shape how the agent behaves.


Environment Variables

Global environment variables that affect OpenVole core:

VariableDescription
VOLE_LOG_LEVELLog level: debug, info, warn, error. Default: info.
VOLE_LOG_FILEPath to log file. Default: .openvole/logs/vole.log.
VOLE_DASHBOARD_PORTControl-plane dashboard (vole serve) HTTP port. Default: 3000.
VOLE_HOMEExplicit OpenVole root for vole serve (overrides current-directory resolution).
VOLE_DEBUGEnable debug mode (true/false).
VOLE_IPC_TIMEOUT_MSIPC timeout for paw communication in ms. Default: 300000.
VOLE_COMPACT_MODELExplicit model for LLM compaction (if unset, uses simple compaction).

Provider-specific env vars are passed to paws via the allow.env config — they are not globally available to all paws.