Configuration reference
Deskhand reads two files. config/config.yaml holds settings and is safe to commit. .env holds secrets. Any value in config/config.yaml can reference an environment variable as ${NAME}; Deskhand refuses to start if a referenced variable is unset, or without the model’s API key.
The config path is the config option of start() in src/main.ts. Relative paths in the config, such as docs.sources[].path, resolve against the working directory: your project folder, or /app in Docker.
If the config is invalid, Deskhand prints the path of the bad key and exits with status 1. At least one chat platform must be enabled, unless you run the console.
Code extensions are not configured here; you import them in src/main.ts. See Code extensions.
config/config.yaml
model:
provider: openai_compatible # openai_compatible | openai | anthropic
base_url: https://api.openai.com/v1
id: gpt-5-mini
context_window: 200000
max_tokens: 16384
reasoning: false
api_key_env: LLM_API_KEY
chat:
slack: { enabled: true }
teams: { enabled: false }
googlechat:
enabled: false
endpoint_url: https://bot.example.com/googlechat
project_number: "123456789012"
http: { port: 3978 }
docs:
sync_interval_minutes: 30
sources:
- type: local
name: runbooks
path: ./docs
url_base: https://github.com/acme/it-docs/blob/main
- type: google_drive
name: it-drive
folder_id: 1AbC...
- type: confluence
name: wiki
site: https://acme.atlassian.net
space_key: IT
parent_page_id: "123456"
mcp:
- name: tickets
command: npx
args: ["-y", "@acme/tickets-mcp"]
env: { TICKETS_TOKEN: "${TICKETS_TOKEN}" }
read_only: [search_tickets, get_ticket]
- name: remote
url: https://mcp.example.com/mcp
headers: { Authorization: "Bearer ${REMOTE_MCP_TOKEN}" }
approvals: { timeout_minutes: 60 }
housecall:
enabled: false
help_url: https://ithousecall.app/o/acme
system_prompt: |
Our VPN client is GlobalProtect. Laptops are managed with Jamf. model
| Key | Meaning |
|---|---|
provider | openai_compatible for any OpenAI-style API, or the built-in openai or anthropic provider. |
id | Model ID sent to the provider. The only key openai and anthropic accept. They read OPENAI_API_KEY and ANTHROPIC_API_KEY. |
base_url | openai_compatible only. API base URL. |
context_window | openai_compatible only. Default 128000. |
max_tokens | openai_compatible only. Default 16384. |
reasoning | openai_compatible only. Set true for reasoning models. Default false. |
api_key_env | openai_compatible only. Name of the environment variable that holds the API key. Default LLM_API_KEY. |
chat and http
| Key | Meaning |
|---|---|
chat.slack.enabled | Connect to Slack over Socket Mode. See Slack. |
chat.teams.enabled | Serve the Teams bot endpoint at /api/messages. See Microsoft Teams. |
chat.googlechat.enabled | Serve the Google Chat endpoint at the path of endpoint_url. See Google Chat. |
chat.googlechat.endpoint_url | The public URL Google Chat calls. Must match the app configuration exactly. Required when enabled. |
chat.googlechat.project_number | The Google Cloud project’s number, from its dashboard. Quote it. Required when enabled. |
http.port | Port for the Teams and Google Chat endpoints. Default 3978. Listens only when Teams or Google Chat is enabled. GET /healthz returns ok. |
docs
| Key | Meaning |
|---|---|
sync_interval_minutes | How often to re-sync every source. Deskhand also syncs on startup. When you remove a source, its documents are deleted at the next sync. |
sources[].type | local, google_drive, or confluence. |
sources[].name | Unique name for the source: lowercase letters, digits, _ or -. |
Source-specific keys are on Local folder, Google Drive, and Confluence.
mcp and approvals
| Key | Meaning |
|---|---|
mcp[] | MCP servers. Each has command (stdio) or url (HTTP), never both. See MCP servers. |
approvals.timeout_minutes | How long an approval waits before it expires. See Approvals. |
housecall and system_prompt
| Key | Meaning |
|---|---|
housecall.enabled | Let Deskhand offer an IT Housecall link. See IT Housecall handoff. |
housecall.help_url | The link it offers. Required when enabled. |
housecall.api_base, housecall.org_slug | Optional, set together: create a fresh session link per offer. |
system_prompt | Optional text added to the agent’s instructions, such as which tools your company uses. |
.env
| Variable | Used for |
|---|---|
LLM_API_KEY | Model API key for openai_compatible. The name is whatever model.api_key_env says. The model’s key is required; an empty value counts as missing. |
OPENAI_API_KEY | Model API key for provider: openai. |
ANTHROPIC_API_KEY | Model API key for provider: anthropic. |
SLACK_BOT_TOKEN, SLACK_APP_TOKEN | Slack bot token (xoxb-) and app-level token (xapp-). |
TEAMS_CLIENT_ID, TEAMS_CLIENT_SECRET, TEAMS_TENANT_ID | Azure Bot app registration. |
GOOGLE_SERVICE_ACCOUNT_FILE | Path to a service account JSON key, used by Google Chat and Google Drive, for example secrets/<file>.json. For Docker, uncomment the secrets volume in deploy/docker-compose.yml. |
CONFLUENCE_EMAIL, CONFLUENCE_API_TOKEN | Atlassian account and API token for Confluence. Use a low-privilege account. |
JIRA_BASE_URL, JIRA_EMAIL, JIRA_API_TOKEN | The starter’s Jira extension, src/extensions/jira.ts. |
Add any other variable you reference with ${NAME} in config/config.yaml, such as tokens for MCP servers.
LLM_API_KEY=sk-...
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...
TEAMS_CLIENT_ID=
TEAMS_CLIENT_SECRET=
TEAMS_TENANT_ID=
GOOGLE_SERVICE_ACCOUNT_FILE=secrets/<file>.json
CONFLUENCE_EMAIL=
CONFLUENCE_API_TOKEN=
JIRA_BASE_URL=
JIRA_EMAIL=
JIRA_API_TOKEN=