Configuration
Where dashsquad keeps its settings and data, and the files, environment variables and flags that you can change.
You configure dashsquad in Desktop. You deploy agents, connect providers and set up Telegram and WhatsApp in the app. Desktop sends the changes to the HQ, and the HQ saves them. You do not have to edit a configuration file.
This page tells you what you can configure and where dashsquad keeps its data. It also gives the files, environment variables and flags for advanced use and for a standalone HQ.
Some paths on this page start with ~/.dash/gateway, the folder of a standalone HQ. For an HQ that Desktop starts, use ~/.dashsquad/<hq id> in its place. See Data folders.
Squad member settings
You set up an agent in the Deploy agent wizard. Later, you can change it on the Configuration tab of the agent page. An agent has these settings:
| Setting | Required | Description |
|---|---|---|
| Name | yes | The name of the squad member. |
| Model | yes | The model of the agent, in provider/model format, for example anthropic/claude-sonnet-4-6. The list shows the models of all providers. You can select only the models of providers that you connected. See AI Providers. |
| Fallback models | no | Models that the agent tries if the primary model is not available. You set them with the model. See Squad. |
| System prompt | no | Instructions that the HQ sends with each request. |
| Tools | no | The tool groups that the agent can use. See Tools. |
| Workspace folder | no | The folder where the file and shell tools of the agent operate. The default is <dataDir>/workspaces/<agent-id>, in the data directory of the HQ. |
| Connectors | no | The MCP servers that the agent can use. |
| Memory | no | Lets the agent keep facts between conversations. Set it on the Memory tab. See Memory. |
| Skills | no | Instructions that the agent can find and load. See Skills. |
The HQ saves your agents to ~/.dash/gateway/agents.json. When the HQ starts again, it loads them from this file.
The Management API has more agent fields that Desktop does not show, for example maxTokens (the maximum length of a reply).
Sub-agents
An agent can give a self-contained task to a sub-agent. A sub-agent is a child agent with its own context, its own tools and its own transcript. The agent can wait for the report, or let the child operate in the background. This function is on by default. The HQ gives the agent and send_message tools, and the older spawn_worker tools, to each agent. See Tools → Sub-agents.
Per-agent settings
Each agent can have a subagents block in its configuration. The HQ saves this block as it is in agents.json.
| Setting | Default | Description |
|---|---|---|
enabled | true | Gives the agent the sub-agent tools. If this key is not set, the HQ reads the older swarm.enabled. If both keys are set, subagents.enabled wins. |
delegation | from the model | auto: the agent delegates by itself. explicit: the agent delegates only when you ask. This value is guidance in the system prompt, not a hard limit. If you do not set it, a model that its provider lists as top tier gets auto. All other models get explicit. |
allowedTypes | all types | The sub-agent types that this agent can start. An empty list means no types. If an entry matches no type, the HQ shows a warning, not an error. |
allowedModels | not set | More models for a sub-agent. A sub-agent can always use the model and the fallback models of the parent. Thus, this list cannot stop a sub-agent that asks for the model of the parent. |
maxConcurrent | 8 | The maximum number of sub-agents of this agent that operate at the same time. |
maxPerTurn | 24 | The maximum number of sub-agents that this agent can start in one turn. |
maxRunSeconds | 1800 | The time limit for one sub-agent, in seconds (30 minutes). |
maxDepth | 3 | How deep sub-agents can nest. A direct child has depth 1. 0 stops all sub-agents for this agent. |
modelAliases | none | Short names for models, for example {"sonnet": "anthropic/claude-sonnet-4-6"}. With aliases, a definition from Claude Code with model: sonnet can work. dashsquad has no default aliases. If a sub-agent asks for an unknown alias, it uses the model of the parent. The tool result tells you this. |
The older swarm block has the same limits with different names: maxConcurrentWorkers, maxWorkersPerRun, maxRunSeconds and allowedModels. If both blocks set a limit, the subagents value wins. Only swarm has maxSteersPerWorker, the maximum number of messages to one child in progress (default 10).
The count of messages to a child stays in memory only. Thus, after an HQ restart, the count starts again at zero.
In Desktop, open the Configuration tab of the agent and edit the Swarm card. The switch writes subagents.enabled. The limit fields write the swarm block. To set delegation, allowedTypes, maxDepth or modelAliases, use PUT /agents/:id in the Management API.
A change to either block applies to the next message of the agent. The HQ makes the agent again with the new values.
HQ defaults
The HQ has a global limit for all agents and gives the per-agent defaults. You can change each value with an environment variable where the HQ operates:
| Setting | Default | Environment variable | Description |
|---|---|---|---|
maxConcurrentWorkersGlobal | 16 | SUBAGENTS_MAX_CONCURRENT_GLOBAL | The maximum number of sub-agents that operate at the same time on this HQ, for all agents. A new sub-agent above this limit fails. |
defaults.maxConcurrentWorkers | 8 | SUBAGENTS_MAX_CONCURRENT | The default for maxConcurrent. |
defaults.maxWorkersPerRun | 24 | SUBAGENTS_MAX_PER_TURN | The default for maxPerTurn. |
defaults.maxRunSeconds | 1800 | SUBAGENTS_MAX_RUN_SECONDS | The default for maxRunSeconds. |
defaults.maxSteersPerWorker | 10 | SWARM_DEFAULT_MAX_STEERS_PER_WORKER | The default number of messages to one child. This value has no SUBAGENTS_ name. |
The HQ also reads the older names: SWARM_MAX_CONCURRENT_WORKERS_GLOBAL, SWARM_DEFAULT_MAX_CONCURRENT_WORKERS, SWARM_DEFAULT_MAX_WORKERS_PER_RUN and SWARM_DEFAULT_MAX_RUN_SECONDS. If both names are set, the SUBAGENTS_ value wins. No environment variable sets the nesting depth. Set subagents.maxDepth for each agent.
Each value must be a positive whole number. If a value is not correct, the HQ writes a warning to its log and uses the default. If Desktop starts the HQ, set the variable in the environment that starts Desktop. Restart the HQ or Desktop to apply a change.
A sub-agent is a conversation. Its transcript is in ~/.dash/gateway/sessions/<agent-name>/<subagent-id>/, near your other conversations. The HQ does not delete transcripts automatically. The HQ no longer writes to old .swarm/ folders from earlier releases. You can delete them.
Sub-agent types
A type is a Markdown file with YAML frontmatter, in the Claude Code subagent format. The frontmatter configures the agent. The body is its system prompt.
---
name: code-reviewer
description: Reviews a diff for correctness bugs. Use after implementing a task.
tools: read, grep, find, bash
disallowedTools: write, edit
model: inherit
skills:
- dash-conventions
maxTurns: 40
background: false
isolation: worktree
---
You are a senior reviewer...| Key | Description |
|---|---|
name | Required. Lowercase letters, digits and hyphens only (^[a-z0-9][a-z0-9-]*$). |
description | Required. Tells the parent when to use this type. |
tools | A comma-separated list of dashsquad tool names, MCP patterns (mcp__<server>, mcp__<server>__<tool>, mcp__*), or agent(<type>, …) to limit the types that this agent can start. If you do not set it, the child gets the tools of the parent that a child can have. |
disallowedTools | Tools to remove. The HQ removes them first. |
model | A provider/model ID, or inherit. |
skills | Skills to load into the prompt at the start. |
maxTurns | The agent stops after more than this number of tool calls and reports its result so far. The call that goes over the limit completes. Thus, the agent stops after maxTurns + 1 calls. You can resume it. |
background | true forces this type to the background. A caller cannot bring it to the foreground. |
isolation | worktree gives the agent its own git checkout of the workspace, made from the current commit. |
Some tools are only for the parent. A child never gets the skill management tools, the connector and MCP management tools, the browser tool, or the four older swarm tools. A child can get the file, shell and web tools, load_skill, task and the MCP tools of the parent. It also gets agent and send_message if the depth limit lets it.
The HQ ignores these Claude Code keys and does not write a log message about them: permissionMode, hooks, mcpServers, memory, effort, color, experimental. If a setting does nothing, look for one of these keys.
The HQ reads definitions from four places. If the same name is in two places, the first match in this table wins:
| Order | Where | Named |
|---|---|---|
| 1 | <workspace>/.dash/agents/*.md, then <workspace>/.claude/agents/*.md | by its name |
| 2 | ~/.dash/gateway/subagents/<agent-name>/*.md | by its name |
| 3 | The agents/*.md files of a plugin | <plugin>:<name> |
| 4 | The built-in types (general-purpose, Explore, Plan) | by name |
Plugin types always have a plugin prefix, so they cannot replace other types. A workspace file with the name explore replaces the built-in Explore. The HQ refuses a file with name: Explore, because names must be lowercase.
Do not expect a sub-agent to get more access than its parent. The HQ gives a child only the tools and MCP servers that the parent has. If no tools remain, the sub-agent does not start.
The HQ reads these files one time and keeps them in memory for each agent. It reads a new or changed file when you save the agent configuration, when a plugin loads again, or when the HQ restarts. The Management API also gives access to definitions:
GET /agents/:id/subagent-typeslists all types that an agent can start, and shows which definition replaced which.GET /agents/:id/subagent-definitionslists the files in the per-agent folder.GET,PUTandDELETE /agents/:id/subagent-definitions/:nameread and write one file.PUTchecks the file before it writes it, and refuses a file that it cannot parse.
Data folders
Desktop keeps its data and the data of each HQ that it starts under one root folder. The default is ~/.dashsquad. To use a different root, set DASHSQUAD_HOME.
~/.dashsquad/
├── desktop/ # Desktop data: settings.json, mc.log and other Desktop files
└── <hq id>/ # one folder for each HQ, named by a UUID
├── hq.json # the ID and the name of the HQ
├── hq.lock # stops a second HQ in the same folder
├── ports.json # the ports of the HQ while it operates
├── logs/
│ └── hq.log # the log of the HQ
├── workspaces/ # one folder for each agent
└── ... # agents, credentials, sessions, skills, plugins, memory, databasesThe folder of an HQ is the data directory of that HQ. On all pages, <dataDir> means the data directory of the HQ. The HQ loads the files in it.
| HQ | Data directory (<dataDir>) |
|---|---|
| An HQ that Desktop starts | ~/.dashsquad/<hq id> |
| A standalone HQ | ~/.dash/gateway, or the folder that you give with --data-dir |
Some pages give paths that start with ~/.dash/gateway. For an HQ that Desktop starts, use ~/.dashsquad/<hq id> in place of ~/.dash/gateway.
To find the folder of your HQ, look in ~/.dashsquad. The hq.json file in each HQ folder has the name of the HQ in its displayName field.
The data directory of Desktop is ~/.dashsquad/desktop. MC_DATA_DIR changes it. See Environment variables.
HQ ports
The HQ listens only on 127.0.0.1. It has two ports:
- The management port: the Management API and the
/mobile/v1API. - The channel port: the
/ws/chatWebSocket.
An HQ that Desktop starts gets two free ports each time it starts. The HQ writes them to ports.json in its data directory, for example:
{
"managementPort": 53187,
"channelPort": 53188,
"pid": 41230,
"startedAt": "2026-10-11T09:30:00.000Z"
}A standalone HQ uses port 9300 for management and port 9200 for the channel server. The --management-port and --channel-port flags change them. A standalone HQ also writes ports.json in its data directory. The HQ deletes ports.json when it stops.
One-time move from older releases
Older releases kept all data in ~/.dash. When a new Desktop starts for the first time, it moves this data into a new HQ folder:
| From | To |
|---|---|
~/.dash/gateway/ (all files) | ~/.dashsquad/<hq id>/ |
~/.dash/workspaces/ | ~/.dashsquad/<hq id>/workspaces/ |
~/.dash/desktop/ | ~/.dashsquad/desktop/ |
~/.dash/logs/gateway.log | ~/.dashsquad/<hq id>/logs/hq.log |
~/.dash/logs/mc.log | ~/.dashsquad/desktop/mc.log |
Desktop also changes the workspace paths in agents.json to the new workspaces/ folder. The relay identity of the HQ moves with the other files. Thus, you do not have to set up remote access again.
Before the move, Desktop writes the file ~/.dash/MOVED-TO-DASHSQUAD.txt. This file gives the new folder and the time of the move. If the move stops before the end, Desktop continues it into the same folder at the next start.
If you set DASHSQUAD_HOME, MC_DATA_DIR or DASH_HOME, Desktop does not do the move. If an older HQ still operates, or the move fails, Desktop shows Your HQ did not start with the reason.
A standalone HQ does not do this move. When it starts without --data-dir, it moves data from the folders of much older releases into ~/.dash. It does not do this if you set DASH_HOME.
Environment variables
Desktop does not need environment variables. Use these variables for advanced cases and for a standalone HQ:
| Variable | Description |
|---|---|
DASHSQUAD_HOME | The root folder for the data of Desktop and of each HQ that Desktop starts (default: ~/.dashsquad). If you set it, Desktop does not move data from ~/.dash. See Data folders. |
DASH_HOME | Legacy. The root folder of a standalone HQ that starts without --data-dir (default: ~/.dash). The HQ keeps its data in <DASH_HOME>/gateway. If you set it, Desktop does not move data from ~/.dash. |
MC_DATA_DIR | Changes the data folder of Desktop. If you do not set DASHSQUAD_HOME, Desktop also keeps its HQ folders in <MC_DATA_DIR>/dashsquad. If you set MC_DATA_DIR, Desktop does not move data from ~/.dash. |
MC_KEYCHAIN_SERVICE | The keychain service name that Desktop uses for its secrets (default: dash-mission-control). Set it with MC_DATA_DIR to give a second Desktop profile its own secrets. Use only letters, numbers, ., _ and -. |
POOL_MAX_SIZE | The maximum number of agent runtimes that the HQ keeps in memory (default: 200). |
DASH_WEB_ORIGINS | A comma-separated list of browser origins that can call the /mobile/v1 API, for example a web app that you host. An HQ with remote access lets https://app.<relay domain> by default. If you set this variable, the HQ uses only your list. An empty value stops all browser access. |
SUBAGENTS_*, SWARM_DEFAULT_* | Sub-agent limits. See HQ defaults. |
BROWSER_MAX_SESSIONS | The number of browsers that the HQ can operate at the same time, for all agents. A whole number from 1 to 10 (default: 3). If the value is not correct, the HQ writes a warning to its log and uses the default. |
DASH_TELEGRAM_ENVIRONMENT | Set to test to connect Telegram bots to the Telegram test environment. Use this only for a bot that you made on the test environment. The default is prod. Another value stops the Telegram channel with an error. |
DASH_TELEGRAM_API_BASE | The URL of a different Telegram Bot API server, for example a local server. The default is https://api.telegram.org. |
DASHSQUAD_CLAUDE_CODE_VERSION | The Claude Code version, for example 2.1.280, that the HQ sends when it uses an Anthropic sign-in (Claude Pro or Max). It has no effect on an API key or on other providers. If you do not set it, the HQ sends the default version of its provider library. The HQ reads the value when it starts. If the value is not in the form <number>.<number>.<number>, the HQ writes a warning to its log and uses the default. |
BROWSER_EXECUTABLE_PATH | The full path to the browser for the browser tool. The HQ tries this path first. If no browser is there, or it does not start, the HQ finds Chrome or Edge as usual. |
The HQ keeps provider API keys in its encrypted credential store. Add them in Desktop on Settings → AI Providers, or with the credentials API of the HQ. A provider environment variable, for example ANTHROPIC_API_KEY, works for chat turns only. The model list, chat titles, the memory sweep and the skill review read only the credential store. See Secrets and AI Providers.
Run the HQ standalone
Desktop starts and manages the HQ for you. To start the HQ without Desktop, for example on a server, use a dashsquad source checkout:
npm run gatewayA standalone HQ uses ~/.dash/gateway for its data and ports 9300 and 9200. See Data folders. Make agents, Telegram and WhatsApp connections, and credentials through its Management API. The HQ accepts these flags:
| Flag | Description |
|---|---|
--management-port <port> | The port of the Management API (default: 9300). |
--channel-port <port> | The port of the WebSocket channel server (default: 9200). |
--data-dir <path> | Changes the data directory of the HQ (default: ~/.dash/gateway). |
--relay-url <url> | The relay that the HQ connects out to. This flag turns on remote access. |
--gateway-id <id> | The ID of the HQ on the relay. If you do not set it, the HQ makes one. |
--control-plane-url <url> | The control plane. With this flag, the HQ gets new relay tokens by itself. |
--verbose | Writes debug messages to the log. |
Give the tokens to the HQ in these environment variables:
| Variable | Description |
|---|---|
DASHSQUAD_HQ_TOKEN | The bearer token for the administrative routes of the Management API. |
DASHSQUAD_HQ_CHAT_TOKEN | The token for the chat socket and the /mobile/v1 API. |
DASHSQUAD_HQ_RELAY_TOKEN | The token that the HQ gives to the relay when it connects. |
DASHSQUAD_HQ_TOKEN=<management-token> DASHSQUAD_HQ_CHAT_TOKEN=<chat-token> npm run gatewayThe HQ removes these variables from its own environment when it starts. The old --token, --chat-token and --relay-token flags still work in this release, but the HQ writes a warning for each one. If you set a variable and its flag, the variable wins.
Both servers listen only on 127.0.0.1. The HQ refuses to start in these cases:
- The management token and the chat token have the same value.
- You set
--relay-urlwithout both the management token and the chat token.
The HQ ignores the old --lan-port flag and writes a warning, because dashsquad removed the local network listener. The HQ has no --config or --secrets flag, and it reads no JSON configuration file at start.
Remote HQ runtime
Desktop can also use an HQ on a different computer, for example a VPS. Open Settings → General. The HQ section with Restart HQ shows the current HQ. Click Change HQ to open Choose an HQ.
| Option | What it does |
|---|---|
| Use this computer | The default. Desktop starts the HQ on this computer and connects over loopback. |
| Connect existing HQ | Connects Desktop to an HQ through its relay address. |
| Self-host on a VPS | Installs the HQ on a server for you. This is the advanced option. |
For Connect existing HQ, fill in HQ name, Management URL, Chat URL, Management token, Chat token and, if your relay needs it, Relay credential. Click Test connection. Desktop enables Use this HQ only after the HQ answers. Desktop saves the URLs in its settings and keeps the tokens in the system credential store.
For Self-host on a VPS, give the SSH connection (Host, User, SSH port, SSH key) and the relay settings. Then click Deploy and connect. Desktop connects over SSH and gets the dashsquad source with git. It builds the HQ, then makes and starts the dash-gateway systemd user service. Then it tests the HQ and saves the relay address. The server must already have Node.js, npm, git and systemd user services.
Credentials
The HQ encrypts provider API keys and bot tokens. It keeps them in ~/.dash/gateway/credentials.enc (AES-256-GCM), with the key in secret.key in the same folder. Desktop keeps the HQ access tokens in the system credential store. See Secrets.
Speech
The HQ keeps the settings for dictation and read-aloud in <dataDir>/speech.json. If a field is not set, the HQ uses these defaults:
| Function | Default |
|---|---|
| Speech to text | openai/whisper-large-v3 through OpenRouter |
| Text to speech | minimax/speech-2.8-turbo through OpenRouter, voice English_expressive_narrator |
Voice IDs are different for each model, for example English_radiant_girl for MiniMax, af_heart for Kokoro, and en-US-Harper:MAI-Voice-2 for MAI-Voice-2. The provider refuses an unknown voice.
Speech has no credential of its own. It uses the openrouter API key that you already added for your models. When that key is available, speech is available.
The settings also have a realtime entry for live voice conversation. This entry is always unavailable, because no provider supplies realtime speech now.
Voice mode
Hands-free voice mode speaks each sentence when it is ready. See dashsquad for iPhone and iPad. The audio format depends on the text-to-speech model:
| Model | Audio |
|---|---|
hexgrad/kokoro-82m, microsoft/mai-voice-2-flash, microsoft/mai-voice-2 | A continuous PCM stream |
| Google Gemini text to speech | PCM, because these models give only PCM. Read-aloud gets them as WAV. |
| All other models, for example the default MiniMax voice | One MP3 clip for each sentence |
Update the HQ and the apps together. An older HQ refuses a request from a newer app to set the dictation language to "Auto".
Message routing
When you send a message while your agent works, the HQ can select Steer or Follow Up for you. The HQ keeps this configuration in <dataDir>/message-routing.json.
| Key | Default | Description |
|---|---|---|
mode | auto | auto: the HQ selects. ask: the app always asks you. |
model | typesafe/jev-1.13 | The decision model, called through OpenRouter. |
steerMinConfidence | 0.8 | The minimum confidence (0 to 1) for an automatic Steer. |
followUpMinConfidence | 0.5 | The minimum confidence (0 to 1) for an automatic Follow Up. |
timeoutMs | 1500 | The time that the HQ waits for a decision before it asks you. A whole number from 100 to 10000. |
baseUrl | the OpenRouter API | Optional. A different endpoint for a compatible decision model. |
The HQ sends the text of the new message, a short part of the current task, and the reply so far to the decision model. It never sends images. To stop this, set mode to ask.
With the default endpoint, the HQ needs your OpenRouter key. If you have no OpenRouter key, the HQ always asks you. A custom baseUrl, for example a local server, needs no OpenRouter key. The HQ never sends the key to a custom endpoint.
To change mode, open Settings → Chat in Desktop and select Route automatically or Always ask. To change the other keys, edit message-routing.json. The HQ reads the file for each decision, so a change applies immediately.
Plugins
Plugins add skills, commands, sub-agent types, hooks, MCP servers, programs and AI providers to the HQ. The HQ finds plugins in the plugins/ folder of its data directory: <dataDir>/plugins/. In this section, <dataDir> means the data directory of the HQ, not the Desktop folder ~/.dashsquad/desktop.
For a step-by-step guide to find, enable, trust and use plugins, see Plugins. You can also manage plugins in Desktop on Settings → Plugins. This section is the reference for the folder layout, config.json, hooks and provider catalogs.
Directory layout
<dataDir>/ # the HQ data directory, for example ~/.dashsquad/<hq id>
└── plugins/
├── config.json
└── <plugin-name>/
├── .claude-plugin/
│ └── plugin.json (optional)
├── skills/
│ └── <skill-name>/
│ └── SKILL.md
├── commands/
│ └── <command-name>.md (optional)
├── agents/
│ └── <agent-name>.md (optional)
├── hooks/
│ └── hooks.json (optional, trusted only)
├── .mcp.json (optional, trusted only)
├── providers/
│ └── <provider>.json (optional, trusted only)
└── bin/ (optional, trusted only)
└── <executable>Each plugin uses the Claude Code layout:
| Folder or file | What it adds | Needs trusted |
|---|---|---|
skills/ | One folder for each skill, with a SKILL.md file (name, description, body). | no |
commands/ | <name>.md files. Each file becomes a /<plugin>:<command> command. See Commands. | no |
agents/ | <name>.md files in the Claude Code subagent format. Each file becomes a sub-agent type. See Plugin sub-agent types. | no |
hooks/hooks.json | Shell commands for events in a run. See Hooks. | yes |
.mcp.json | More MCP servers for the HQ. | yes |
providers/ | <provider>.json catalogs that add AI providers and models. See LLM providers. | yes |
bin/ | Programs that the HQ adds to the start of its PATH. | yes |
The .claude-plugin/plugin.json manifest is optional. It can set name, displayName, description, version and other details. If there is no manifest, the plugin has the name of its folder.
Enable a plugin
Create or edit <dataDir>/plugins/config.json:
{
"<plugin-name>": { "enabled": true }
}The HQ ignores a plugin that is not in config.json, or that has "enabled": false.
Trust a plugin
enabled is sufficient for skills/, commands/ and agents/, because these are Markdown and run no code. Hooks, MCP servers, bin/ programs and provider catalogs run code or get credentials. They also need "trusted": true:
{
"<plugin-name>": { "enabled": true, "trusted": true }
}A plugin that is enabled but not trusted still gives its skills, commands and sub-agent types. The HQ ignores its hooks, MCP servers, bin/ folder and provider catalogs.
Set trusted only for a plugin whose code you examined. Trusted code runs inside the HQ process.
Load a plugin from a path
An entry in config.json can point to a plugin folder anywhere on the disk with a path field. Then the plugin does not have to be under <dataDir>/plugins/. This is useful when you develop a plugin in your own folder:
{
"my-dev-plugin": { "path": "/abs/or/relative/dir", "trusted": true }
}A path entry is enabled automatically. It still needs "trusted": true for hooks, MCP servers, bin/ programs and provider catalogs.
Commands
Each commands/<name>.md file becomes a command with the name /<plugin>:<name>. For example, a plugin with the name my-skills and the file commands/triage.md gives /my-skills:triage. The body of the file is the command text. The optional frontmatter name changes the command name, and description gives a description. Commands need only enabled.
Plugin sub-agent types
Each agents/<name>.md file is a sub-agent type that an agent can start with the agent tool. A plugin with the name my-skills and the file agents/reviewer.md gives the type my-skills:reviewer. If the file has a name in its frontmatter, the type name is <plugin>:<frontmatter-name>. These files need only enabled.
A plugin sub-agent type operates in its own context with its own transcript. The HQ applies its tools and model frontmatter, but a sub-agent never gets a tool or MCP server that its parent does not have. It is not a command, and load_skill cannot load it. See Sub-agent types for the file format.
In older releases, a plugin agents/*.md file was a specialist that loaded into the main conversation as /<plugin>:<agent>. It is now a sub-agent type.
MCP servers and executables
The HQ reads the .mcp.json file of a trusted plugin when it starts. It adds the servers to its MCP manager and saves them with your other MCP servers. If one server does not start, the HQ writes this to the log and continues with the other servers. The HQ adds the bin/ folder of a trusted plugin to the start of its PATH. Thus, these programs have priority over system programs with the same name.
Hooks
A trusted plugin can add hooks in hooks/hooks.json. Hooks are shell commands that run at points in an agent run. The file uses the Claude Code hooks format. The HQ ignores the hooks of a plugin that is not trusted.
| Event | When it occurs | What the hook can do |
|---|---|---|
SessionStart | At the start of a run, before the model | Add context to the system prompt |
UserPromptSubmit | After a Telegram or WhatsApp message passes the routing rules, before the agent | Block the message, or add context to the prompt |
PreToolUse | Before a tool runs (matched by tool name) | Let the tool call continue, or stop it |
PostToolUse | After a tool runs (matched by tool name) | Block the result, or add context |
Stop | After the run is complete | Clean up. The HQ writes the output to the log. |
SubagentStart | When a sub-agent starts (matched by its role) | Record or report the start |
SubagentStop | When a sub-agent completes, fails, is cancelled, or reaches its turn limit (matched by its role) | Record or report the result |
The role of a sub-agent is the name that the agent call gave. If the call gave no name, the role is the type name, for example general-purpose or my-plugin:reviewer. For the older spawn_worker tool, the role is its role argument. The role is never the description.
Each event has a list of matcher groups. Each group has an optional matcher and a hooks list. The matcher is a tool name, a list of names with | between them, or a regular expression. If you omit it, or use *, it matches all. Each hook is { "type": "command", "command": "...", "timeout": <seconds> }.
{
"PreToolUse": [
{
"matcher": "bash",
"hooks": [
{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/guard.sh" }
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/inject-context.sh" }
]
}
]
}A hook command gets the Claude Code event data as JSON on stdin, for example tool_name, tool_input, prompt, session_id and cwd. It answers with its exit code and stdout:
- Exit
0: continue. To stop the call, add context or replace the tool input, print a JSON object with ahookSpecificOutputfield. For example:{"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason":"..."}}or{"hookSpecificOutput":{"additionalContext":"..."}}. - Exit
2: block. The text on stderr becomes the reason.
The HQ replaces these values in a command:
| Value | Replaced with |
|---|---|
${CLAUDE_PLUGIN_ROOT} | The full path of the plugin folder |
${CLAUDE_PROJECT_DIR} | The current folder of the run |
${CLAUDE_PLUGIN_DATA} | A data folder for the plugin, <dataDir>/plugins/data/<plugin-name> |
Hook failures do not stop a run. The HQ ignores a hook that has an error or a timeout. It also ignores an exit code other than 0 or 2, and JSON that is not correct. The run continues as if the hook said yes. A hook cannot stop the HQ.
Where each event occurs. SessionStart, PreToolUse, PostToolUse and Stop occur in each agent run, from Desktop chat or from Telegram and WhatsApp. UserPromptSubmit occurs only for Telegram and WhatsApp messages, not for chat in the dashsquad apps.
SubagentStart and SubagentStop occur for each sub-agent. Their stdin data has worker_id, role and, at stop, the final status (done, failed, cancelled or max_turns). These two events can only observe. They cannot block a sub-agent, and the HQ writes their output to the log. If an HQ restart stopped a sub-agent, the HQ marks it at start without these hooks.
LLM providers
A trusted plugin can add an AI provider with one or more providers/<provider>.json catalog files. A catalog gives a base URL, an API type and a list of models. After the plugin loads, its models show in the model list of Desktop as <id>/<model-id>. Any agent can use them, as with the built-in providers.
Add only provider plugins that you trust. A provider catalog needs "trusted": true. The provider sees each prompt that you send to its models, gets your API key, and selects the network endpoint for the requests.
Each catalog file is one JSON object:
{
"id": "myllm",
"label": "My LLM",
"credentialPrefix": "myllm-api-key",
"baseUrl": "https://api.myllm.example/v1",
"api": "openai-completions",
"models": [
{
"id": "my-fast",
"name": "My Fast",
"contextWindow": 128000,
"maxTokens": 8192,
"reasoning": false,
"cost": { "input": 0.5, "output": 1.5, "cacheRead": 0, "cacheWrite": 0 }
}
]
}| Field | Required | Description |
|---|---|---|
id | yes | The provider ID and the <id>/<model> prefix. Lowercase letters, digits and hyphens. |
label | yes | The provider name that people see. |
baseUrl | yes | The API base URL that the HQ calls. |
api | yes | The API type, for example openai-completions, openai-responses or anthropic-messages. The HQ also accepts azure-openai-responses, openai-codex-responses, mistral-conversations, bedrock-converse-stream, google-generative-ai and google-vertex. |
credentialPrefix | yes | Must be <id>-api-key, for example myllm-api-key. The HQ refuses a catalog with a different value. |
models[] | yes | The known models. Each model needs id, contextWindow and maxTokens. name, reasoning, thinkingLevelMap, cost, input, headers and compat are optional. |
dynamicModels | no | Accepts any model ID. See below. |
dynamicModelDefaults | no | { "contextWindow", "maxTokens" } for model IDs that are not in the list. |
placeholderKey | no | A key for local servers that need no key. See below. |
Models with thinking levels. Some models refuse a request that does not ask for thinking. Other models need a specific value. Add thinkingLevelMap to these models, so that the HQ sends a value that they accept:
{
"id": "my-reasoner",
"contextWindow": 272000,
"maxTokens": 128000,
"reasoning": true,
"thinkingLevelMap": { "off": null, "high": "HIGH" }
}The keys are off, minimal, low, medium, high and xhigh:
"off": nullmeans that the model needs thinking. The HQ then uses the lowest level that the model accepts. It does not turn thinking off."off": "none"means that you can turn thinking off, but the provider must get this value. The GPT-5 family works this way.- A different string changes the level to the value that the provider expects, for example
"high": "HIGH". nullon a different level means that the model does not accept that level.
If the model accepts all levels, do not add thinkingLevelMap. If you see an error about a missing or incorrect reasoning_effort value, set this field.
Store the API key. Save the key in the credential store as <id>-api-key:<name>, for example myllm-api-key:default. The easiest way is Settings → AI Providers in Desktop, which writes that key for you. You can also use the credentials API of the HQ.
If a provider has more than one key, the HQ uses the first key.
Providers that accept any model. Set "dynamicModels": true and "dynamicModelDefaults": { "contextWindow": ..., "maxTokens": ... }. Then the HQ accepts any model ID of the provider. This is useful for an aggregator such as OpenRouter, where the catalog cannot list all models. The listed models still show in the model list. The HQ accepts an ID that is not in the list and uses the defaults for its size. Without dynamicModelDefaults, the HQ refuses an ID that is not in the list.
Local servers without a key. For a local server that needs no real key, such as Ollama, set "placeholderKey": "ollama". Any string that is not empty is correct. If no real key is stored, the HQ uses this value.
If you already have an endpoint that is compatible with OpenAI or Anthropic, write a catalog with its baseUrl, api and model list.
Example
This example uses the folder of a standalone HQ. For an HQ that Desktop starts, use ~/.dashsquad/<hq id>/plugins in place of ~/.dash/gateway/plugins.
# Create the plugin folder and a skill
mkdir -p ~/.dash/gateway/plugins/my-skills/skills/summarize
cat > ~/.dash/gateway/plugins/my-skills/skills/summarize/SKILL.md <<'EOF'
---
name: summarize
description: Summarize any text concisely
---
Summarize the provided text in three sentences or fewer.
EOF
# Enable the plugin
echo '{ "my-skills": { "enabled": true } }' \
> ~/.dash/gateway/plugins/config.jsonWhen the HQ starts, it writes [plugins] loaded 'my-skills' (skills) to its log. An agent with the load_skill tool can then use any skill from the plugin. See Tools.