Skip to main content
Pylon uses a single machine-level config file at ~/.pylon/config.yaml to define how your daemon runs: which port it listens on, what Docker limits apply to agent containers, which notification channel receives alerts by default, and which host CLI tools agents can call. Running pylon setup creates this file for you and walks you through the core options. Secrets such as bot tokens and API keys belong in ~/.pylon/.env (one KEY=VALUE pair per line); Pylon loads that file automatically on pylon start and never reads it into version control.

Secrets file

Store sensitive values in ~/.pylon/.env:
TELEGRAM_BOT_TOKEN=7123456789:AAF...
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...
ANTHROPIC_API_KEY=sk-ant-...
Reference these values anywhere in config.yaml using ${VAR_NAME} syntax. Pylon expands them at startup, so the file itself never contains raw secrets.

Annotated config.yaml

# Config schema version. Currently always 1.
version: 1

server:
  # Port the HTTP daemon listens on.
  port: 8080
  # Network interface to bind. Use 0.0.0.0 to accept connections on all interfaces.
  host: "0.0.0.0"
  # Base URL used when printing webhook URLs. Set this to your externally-reachable
  # address so Pylon can tell you the full URL to register with third-party services.
  public_url: "https://myserver.example.com"

defaults:
  channel:
    # Notification backend. Options: telegram, slack, webhook, stdout
    type: telegram
    telegram:
      bot_token: "${TELEGRAM_BOT_TOKEN}"
      chat_id: -1001234567890
      # Telegram user IDs allowed to click Approve/Ignore on jobs.
      allowed_users:
        - 987654321
    # Uncomment for Slack instead:
    # slack:
    #   bot_token: "${SLACK_BOT_TOKEN}"
    #   app_token: "${SLACK_APP_TOKEN}"
    #   channel_id: "C0123456789"
    #   allowed_users:
    #     - "U0123456789"

  agent:
    # Agent runtime. Options: claude, opencode
    type: claude
    claude:
      # Docker image used for Claude Code agent containers.
      image: "pylon/agent-claude"
      # Auth method. Options: oauth, api_key
      auth: oauth
      # Path to your local ~/.claude/ OAuth session, mounted read-only into the container.
      oauth_path: "/home/youruser/.claude"
    # Uncomment for OpenCode instead:
    # opencode:
    #   image: "pylon/agent-opencode"
    #   # "none" uses OpenCode Zen (no API key required). "api-key" reads from the provider env var.
    #   auth: "none"
    #   # LLM provider. Options: anthropic, openai, google
    #   provider: anthropic

docker:
  # Maximum number of agent containers that can run at the same time.
  max_concurrent: 3
  # Default timeout for any agent job that does not set its own timeout.
  default_timeout: "15m"

# Host CLI tools that agents can call via Pylon's exec gateway.
# Each tool is an executable on the host exposed to agent containers by name.
tools:
  - name: deploy
    path: /usr/local/bin/deploy.sh
    timeout: "60s"
  - name: lint
    path: /usr/local/bin/eslint
  - name: notify
    path: /usr/local/bin/notify.sh
    timeout: "10s"

Field reference

version

version
integer
required
Config schema version. Set this to 1. Pylon uses this to detect breaking changes in future releases.

server

server.port
integer
default:"8080"
HTTP port the Pylon daemon listens on for incoming webhook requests.
server.host
string
default:"0.0.0.0"
Network interface to bind. 0.0.0.0 accepts connections on all interfaces. Use 127.0.0.1 to restrict to localhost.
server.public_url
string
Base URL Pylon uses when printing webhook registration URLs (for example, https://myserver.example.com). Individual pylons can override this with their own trigger.public_url. If unset, Pylon falls back to http://<host>:<port>.

defaults.channel

defaults.channel.type
string
required
Default notification backend for all pylons. Accepted values: telegram, slack, webhook, stdout. Individual pylons can override this.
defaults.channel.telegram.bot_token
string
Telegram bot token from @BotFather. Use ${TELEGRAM_BOT_TOKEN} to pull from .env. Required when type is telegram.
defaults.channel.telegram.chat_id
integer
Telegram chat or group ID. Use a negative number for groups and supergroups. Required when type is telegram.
defaults.channel.telegram.allowed_users
integer[]
List of Telegram user IDs that may click the Approve or Ignore buttons on job notifications. If empty, approval buttons are not shown.
defaults.channel.slack.bot_token
string
Slack bot token (starts with xoxb-). Required when type is slack.
defaults.channel.slack.app_token
string
Slack app-level token (starts with xapp-). Used for Socket Mode. Required when type is slack.
defaults.channel.slack.channel_id
string
Slack channel ID (starts with C). Required when type is slack.
defaults.channel.slack.allowed_users
string[]
List of Slack user IDs that may approve or ignore jobs. If empty, approval actions are not shown.

defaults.agent

defaults.agent.type
string
default:"claude"
Default agent runtime. Accepted values: claude, opencode.
defaults.agent.claude.image
string
default:"pylon/agent-claude"
Docker image for Claude Code agent containers.
defaults.agent.claude.auth
string
default:"oauth"
Authentication method for Claude. oauth mounts your local OAuth session; api_key reads ANTHROPIC_API_KEY from the environment.
defaults.agent.claude.oauth_path
string
Path to your local ~/.claude/ directory. Pylon mounts it read-only into each agent container. Required when auth is oauth.
defaults.agent.opencode.image
string
default:"pylon/agent-opencode"
Docker image for OpenCode agent containers.
defaults.agent.opencode.auth
string
default:"none"
Authentication method for OpenCode. none uses OpenCode Zen (no API key required); api-key reads the relevant provider environment variable.
defaults.agent.opencode.provider
string
LLM provider when auth is api-key. Accepted values: anthropic, openai, google. Pylon automatically maps each provider to its standard API key environment variable (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY).

docker

docker.max_concurrent
integer
default:"3"
Maximum number of agent containers that can run simultaneously. Jobs that arrive when all slots are occupied are currently rejected.
docker.default_timeout
string
default:"15m"
Default maximum run time for a single agent job. Accepts Go duration strings such as 10m, 1h30m. Individual pylons can override this with agent.timeout.

tools

The tools list defines host CLI programs that agent containers can call through Pylon’s exec gateway. By default, agent containers are isolated Docker environments with no access to your host filesystem or binaries. This list is the controlled way to bridge that gap — you choose exactly which executables to expose, and agents can only call tools that are listed.
tools
ToolConfig[]
List of host CLI tools exposed to agent containers via Pylon’s exec gateway. Each entry specifies a name, an absolute path to the executable, and an optional timeout.
tools[].name
string
required
Identifier used to reference this tool in per-pylon agent.tools lists and in agent instructions. Choose a short, descriptive name without spaces.
tools[].path
string
required
Absolute path to the executable on the host machine. The file must be executable by the user running pylon start.
tools[].timeout
string
default:"30s"
Maximum time Pylon waits for a single tool invocation to complete. Accepts Go duration strings such as 30s, 2m. If the tool does not finish within this limit, Pylon terminates the call and returns an error to the agent.

How tools work

The exec gateway proxies calls from inside the agent container to named executables on the host. When an agent invokes a tool by name, Pylon looks up the matching entry in the resolved tools list, runs the executable on the host with the same OS user privileges as the pylon start process, captures its output, and returns it to the agent. This lets agents trigger real workflows — deployments, linters, build commands — without needing a privileged container or a shared filesystem mount.

Per-pylon override

Individual pylons can override the global tools list by setting agent.tools in their pylon.yaml. When agent.tools is present (even as an empty list), it replaces the global list entirely for that pylon. When agent.tools is absent, Pylon falls back to the global tools list defined here. This lets you give individual pylons a restricted subset of tools, or grant a specific pylon access to a tool not available globally.
# In a pylon.yaml -- overrides the global tools list for this pylon only.
agent:
  tools:
    - name: deploy
      path: /usr/local/bin/deploy.sh
      timeout: "60s"

Security model

Agents cannot run arbitrary commands on your host. They can only call tools by name through the exec gateway, and only tools that appear in the pylon’s resolved tools list are reachable. If an agent attempts to call a tool that is not listed, the gateway rejects the call.
Each listed executable runs with the same OS user privileges as the pylon start process. Avoid listing tools that have broad write access to your filesystem or that accept untrusted input without sanitization.