Docs menu

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

KeyMeaning
provideropenai_compatible for any OpenAI-style API, or the built-in openai or anthropic provider.
idModel ID sent to the provider. The only key openai and anthropic accept. They read OPENAI_API_KEY and ANTHROPIC_API_KEY.
base_urlopenai_compatible only. API base URL.
context_windowopenai_compatible only. Default 128000.
max_tokensopenai_compatible only. Default 16384.
reasoningopenai_compatible only. Set true for reasoning models. Default false.
api_key_envopenai_compatible only. Name of the environment variable that holds the API key. Default LLM_API_KEY.

chat and http

KeyMeaning
chat.slack.enabledConnect to Slack over Socket Mode. See Slack.
chat.teams.enabledServe the Teams bot endpoint at /api/messages. See Microsoft Teams.
chat.googlechat.enabledServe the Google Chat endpoint at the path of endpoint_url. See Google Chat.
chat.googlechat.endpoint_urlThe public URL Google Chat calls. Must match the app configuration exactly. Required when enabled.
chat.googlechat.project_numberThe Google Cloud project’s number, from its dashboard. Quote it. Required when enabled.
http.portPort for the Teams and Google Chat endpoints. Default 3978. Listens only when Teams or Google Chat is enabled. GET /healthz returns ok.

docs

KeyMeaning
sync_interval_minutesHow 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[].typelocal, google_drive, or confluence.
sources[].nameUnique name for the source: lowercase letters, digits, _ or -.

Source-specific keys are on Local folder, Google Drive, and Confluence.

mcp and approvals

KeyMeaning
mcp[]MCP servers. Each has command (stdio) or url (HTTP), never both. See MCP servers.
approvals.timeout_minutesHow long an approval waits before it expires. See Approvals.

housecall and system_prompt

KeyMeaning
housecall.enabledLet Deskhand offer an IT Housecall link. See IT Housecall handoff.
housecall.help_urlThe link it offers. Required when enabled.
housecall.api_base, housecall.org_slugOptional, set together: create a fresh session link per offer.
system_promptOptional text added to the agent’s instructions, such as which tools your company uses.

.env

VariableUsed for
LLM_API_KEYModel 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_KEYModel API key for provider: openai.
ANTHROPIC_API_KEYModel API key for provider: anthropic.
SLACK_BOT_TOKEN, SLACK_APP_TOKENSlack bot token (xoxb-) and app-level token (xapp-).
TEAMS_CLIENT_ID, TEAMS_CLIENT_SECRET, TEAMS_TENANT_IDAzure Bot app registration.
GOOGLE_SERVICE_ACCOUNT_FILEPath 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_TOKENAtlassian account and API token for Confluence. Use a low-privilege account.
JIRA_BASE_URL, JIRA_EMAIL, JIRA_API_TOKENThe 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=