Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.pylon.to/llms.txt

Use this file to discover all available pages before exploring further.

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, and which notification channel receives alerts by default. 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 files

Pylon reads secrets from two .env files and substitutes them into config fields that use ${VAR_NAME} syntax. Config files stay free of raw secrets, and you can commit them to a repo without leaking credentials.

File locations

FileScopePurpose
~/.pylon/.envGlobalSecrets shared across every pylon. Loaded once when pylon start boots the daemon.
~/.pylon/pylons/<name>/.envPer-pylonSecrets scoped to one pylon. Loaded fresh for each job so edits take effect without restarting the daemon.

File format

One KEY=VALUE pair per line. Lines starting with # are comments. Do not wrap values in quotes and do not use a shell export prefix.
# ~/.pylon/.env
ANTHROPIC_API_KEY=sk-ant-...
TELEGRAM_BOT_TOKEN=7123456789:AAF...
# ~/.pylon/pylons/sentry-triage/.env
SENTRY_WEBHOOK_SECRET=whsec_...
SENTRY_AUTH_TOKEN=sntrys_...
Set restrictive permissions on every .env file: chmod 600 ~/.pylon/.env. pylon setup writes new files with mode 0600 automatically, but files you create by hand start with your umask default.

Referencing secrets in YAML

Use ${VAR_NAME} anywhere a string value is accepted:
# config.yaml
defaults:
  channel:
    telegram:
      bot_token: "${TELEGRAM_BOT_TOKEN}"

# pylon.yaml
trigger:
  secret: "${SENTRY_WEBHOOK_SECRET}"
  signature_header: "Sentry-Hook-Signature"
agent:
  api_key: "${ANTHROPIC_API_KEY}"
  env:
    DATABASE_URL: "${DATABASE_URL}"
Fields that commonly hold secrets: trigger.secret, channel.telegram.bot_token, channel.slack.bot_token, channel.slack.app_token, agent.api_key, and any value under agent.env.

Resolution order

When Pylon expands ${VAR_NAME} for a given pylon it checks sources in this order:
  1. The pylon’s own ~/.pylon/pylons/<name>/.env
  2. The process environment (which includes anything the global ~/.pylon/.env loaded at startup, plus whatever was already exported in your shell)
Per-pylon values win. Shell-exported values win over the global .env file — the global loader only sets a variable if it is not already defined.
Per-pylon .env files let each pylon use clean, generic names like TELEGRAM_BOT_TOKEN without worrying about collisions. When the same pylon needs two different tokens (for example, one Telegram bot for alerts and another for debug messages), suffix the names: TELEGRAM_BOT_TOKEN_ALERTS, TELEGRAM_BOT_TOKEN_DEBUG.
If a referenced variable is unset, Pylon rejects the config at startup with a message like telegram.bot_token references ${TELEGRAM_BOT_TOKEN} but it is not set. Run pylon doctor to surface these errors before you rely on the pylon at runtime.

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: "ghcr.io/pylonto/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: "ghcr.io/pylonto/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"

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:"ghcr.io/pylonto/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:"ghcr.io/pylonto/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.