dashsquad

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:

SettingRequiredDescription
NameyesThe name of the squad member.
ModelyesThe 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 modelsnoModels that the agent tries if the primary model is not available. You set them with the model. See Squad.
System promptnoInstructions that the HQ sends with each request.
ToolsnoThe tool groups that the agent can use. See Tools.
Workspace foldernoThe 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.
ConnectorsnoThe MCP servers that the agent can use.
MemorynoLets the agent keep facts between conversations. Set it on the Memory tab. See Memory.
SkillsnoInstructions 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.

SettingDefaultDescription
enabledtrueGives 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.
delegationfrom the modelauto: 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.
allowedTypesall typesThe 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.
allowedModelsnot setMore 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.
maxConcurrent8The maximum number of sub-agents of this agent that operate at the same time.
maxPerTurn24The maximum number of sub-agents that this agent can start in one turn.
maxRunSeconds1800The time limit for one sub-agent, in seconds (30 minutes).
maxDepth3How deep sub-agents can nest. A direct child has depth 1. 0 stops all sub-agents for this agent.
modelAliasesnoneShort 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:

SettingDefaultEnvironment variableDescription
maxConcurrentWorkersGlobal16SUBAGENTS_MAX_CONCURRENT_GLOBALThe 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.maxConcurrentWorkers8SUBAGENTS_MAX_CONCURRENTThe default for maxConcurrent.
defaults.maxWorkersPerRun24SUBAGENTS_MAX_PER_TURNThe default for maxPerTurn.
defaults.maxRunSeconds1800SUBAGENTS_MAX_RUN_SECONDSThe default for maxRunSeconds.
defaults.maxSteersPerWorker10SWARM_DEFAULT_MAX_STEERS_PER_WORKERThe 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...
KeyDescription
nameRequired. Lowercase letters, digits and hyphens only (^[a-z0-9][a-z0-9-]*$).
descriptionRequired. Tells the parent when to use this type.
toolsA 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.
disallowedToolsTools to remove. The HQ removes them first.
modelA provider/model ID, or inherit.
skillsSkills to load into the prompt at the start.
maxTurnsThe 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.
backgroundtrue forces this type to the background. A caller cannot bring it to the foreground.
isolationworktree 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:

OrderWhereNamed
1<workspace>/.dash/agents/*.md, then <workspace>/.claude/agents/*.mdby its name
2~/.dash/gateway/subagents/<agent-name>/*.mdby its name
3The agents/*.md files of a plugin<plugin>:<name>
4The 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-types lists all types that an agent can start, and shows which definition replaced which.
  • GET /agents/:id/subagent-definitions lists the files in the per-agent folder.
  • GET, PUT and DELETE /agents/:id/subagent-definitions/:name read and write one file. PUT checks 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, databases

The 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.

HQData 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:

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:

ports.json
{
  "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:

FromTo
~/.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:

VariableDescription
DASHSQUAD_HOMEThe 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_HOMELegacy. 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_DIRChanges 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_SERVICEThe 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_SIZEThe maximum number of agent runtimes that the HQ keeps in memory (default: 200).
DASH_WEB_ORIGINSA 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_SESSIONSThe 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_ENVIRONMENTSet 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_BASEThe URL of a different Telegram Bot API server, for example a local server. The default is https://api.telegram.org.
DASHSQUAD_CLAUDE_CODE_VERSIONThe 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_PATHThe 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 gateway

A 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:

FlagDescription
--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.
--verboseWrites debug messages to the log.

Give the tokens to the HQ in these environment variables:

VariableDescription
DASHSQUAD_HQ_TOKENThe bearer token for the administrative routes of the Management API.
DASHSQUAD_HQ_CHAT_TOKENThe token for the chat socket and the /mobile/v1 API.
DASHSQUAD_HQ_RELAY_TOKENThe token that the HQ gives to the relay when it connects.
DASHSQUAD_HQ_TOKEN=<management-token> DASHSQUAD_HQ_CHAT_TOKEN=<chat-token> npm run gateway

The 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-url without 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.

OptionWhat it does
Use this computerThe default. Desktop starts the HQ on this computer and connects over loopback.
Connect existing HQConnects Desktop to an HQ through its relay address.
Self-host on a VPSInstalls 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:

FunctionDefault
Speech to textopenai/whisper-large-v3 through OpenRouter
Text to speechminimax/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:

ModelAudio
hexgrad/kokoro-82m, microsoft/mai-voice-2-flash, microsoft/mai-voice-2A continuous PCM stream
Google Gemini text to speechPCM, because these models give only PCM. Read-aloud gets them as WAV.
All other models, for example the default MiniMax voiceOne 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.

KeyDefaultDescription
modeautoauto: the HQ selects. ask: the app always asks you.
modeltypesafe/jev-1.13The decision model, called through OpenRouter.
steerMinConfidence0.8The minimum confidence (0 to 1) for an automatic Steer.
followUpMinConfidence0.5The minimum confidence (0 to 1) for an automatic Follow Up.
timeoutMs1500The time that the HQ waits for a decision before it asks you. A whole number from 100 to 10000.
baseUrlthe OpenRouter APIOptional. 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 fileWhat it addsNeeds 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.jsonShell commands for events in a run. See Hooks.yes
.mcp.jsonMore 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.

EventWhen it occursWhat the hook can do
SessionStartAt the start of a run, before the modelAdd context to the system prompt
UserPromptSubmitAfter a Telegram or WhatsApp message passes the routing rules, before the agentBlock the message, or add context to the prompt
PreToolUseBefore a tool runs (matched by tool name)Let the tool call continue, or stop it
PostToolUseAfter a tool runs (matched by tool name)Block the result, or add context
StopAfter the run is completeClean up. The HQ writes the output to the log.
SubagentStartWhen a sub-agent starts (matched by its role)Record or report the start
SubagentStopWhen 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 a hookSpecificOutput field. 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:

ValueReplaced 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 }
    }
  ]
}
FieldRequiredDescription
idyesThe provider ID and the <id>/<model> prefix. Lowercase letters, digits and hyphens.
labelyesThe provider name that people see.
baseUrlyesThe API base URL that the HQ calls.
apiyesThe 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.
credentialPrefixyesMust be <id>-api-key, for example myllm-api-key. The HQ refuses a catalog with a different value.
models[]yesThe known models. Each model needs id, contextWindow and maxTokens. name, reasoning, thinkingLevelMap, cost, input, headers and compat are optional.
dynamicModelsnoAccepts any model ID. See below.
dynamicModelDefaultsno{ "contextWindow", "maxTokens" } for model IDs that are not in the list.
placeholderKeynoA 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": null means 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".
  • null on 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.json

When 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.

On this page