Skip to main content
Reference Last updated: 8 March 2026

Configuration

Untether reads configuration from ~/.untether/untether.toml.

Untether reads configuration from ~/.untether/untether.toml.

If you expect to edit config while Untether is running, set:

=== “untether config”

```sh
untether config set watch_config true
```

=== “toml”

```toml
watch_config = true
```

Top-level keys

KeyTypeDefaultNotes
watch_configboolfalseHot-reload config changes (transport excluded).
default_enginestring"codex"Default engine id for new threads.
default_projectstring|nullnullDefault project alias.
transportstring"telegram"Transport backend id.

transports.telegram

=== “untether config”

```sh
untether config set transports.telegram.bot_token "..."
untether config set transports.telegram.chat_id 123
```

=== “toml”

```toml
[transports.telegram]
bot_token = "..."
chat_id = 123
```
KeyTypeDefaultNotes
bot_tokenstring(required)Telegram bot token from @BotFather.
chat_idint(required)Default chat id.
allowed_user_idsint[][]Allowed sender user ids. Empty disables sender filtering; when set, only these users can interact (including DMs).
message_overflow"trim"|"split""split"How to handle long final responses.
forward_coalesce_sfloat1.0Quiet window for combining a prompt with immediately-following forwarded messages; set 0 to disable.
voice_transcriptionboolfalseEnable voice note transcription.
voice_max_bytesint10485760Max voice note size (bytes).
voice_transcription_modelstring"gpt-4o-mini-transcribe"OpenAI transcription model name.
voice_transcription_base_urlstring|nullnullOverride base URL for voice transcription only.
voice_transcription_api_keystring|nullnullOverride API key for voice transcription only.
session_mode"stateless"|"chat""stateless"Auto-resume mode. Onboarding sets "chat" for assistant/workspace.
show_resume_linebooltrueShow resume line in message footer. Onboarding sets false for assistant/workspace.

When allowed_user_ids is set, updates without a sender id (for example, some channel posts) are ignored.

transports.telegram.topics

KeyTypeDefaultNotes
enabledboolfalseEnable forum-topic features.
scope"auto"|"main"|"projects"|"all""auto"Where topics are managed.

transports.telegram.files

KeyTypeDefaultNotes
enabledboolfalseEnable /file put and /file get.
auto_putbooltrueAuto-save uploads.
auto_put_mode"upload"|"prompt""upload"Whether uploads also start a run.
uploads_dirstring"incoming"Relative path inside the repo/worktree.
allowed_user_idsint[][]Allowed senders for file transfer; empty allows private chats (group usage requires admin).
deny_globsstring[](defaults)Glob denylist (e.g. .git/**, **/*.pem).

File size limits (not configurable):

  • uploads: 20 MiB
  • downloads: 50 MiB

projects.<alias>

=== “untether config”

```sh
untether config set projects.happy-gadgets.path "~/dev/happy-gadgets"
untether config set projects.happy-gadgets.worktrees_dir ".worktrees"
untether config set projects.happy-gadgets.default_engine "claude"
untether config set projects.happy-gadgets.worktree_base "master"
untether config set projects.happy-gadgets.chat_id -1001234567890
```

=== “toml”

```toml
[projects.happy-gadgets]
path = "~/dev/happy-gadgets"
worktrees_dir = ".worktrees"
default_engine = "claude"
worktree_base = "master"
chat_id = -1001234567890
```
KeyTypeDefaultNotes
pathstring(required)Repo root (expands ~). Relative paths are resolved against the config directory.
worktrees_dirstring".worktrees"Worktree root (relative to path unless absolute).
default_enginestring|nullnullPer-project default engine.
worktree_basestring|nullnullBase branch for new worktrees.
chat_idint|nullnullBind a Telegram chat to this project.

Legacy config note: top-level bot_token / chat_id are auto-migrated into [transports.telegram] on startup.

Plugins

plugins.enabled

=== “untether config”

```sh
untether config set plugins.enabled '["untether-transport-slack", "untether-engine-acme"]'
```

=== “toml”

```toml
[plugins]
enabled = ["untether-transport-slack", "untether-engine-acme"]
```
  • enabled = [] (default) means “load all installed plugins”.
  • If non-empty, only distributions with matching names are visible (case-insensitive).

plugins.<id>

Plugin-specific configuration lives under [plugins.<id>] and is passed to command plugins as ctx.plugin_config.

Controls what appears in the message footer after a run completes.

=== “toml”

```toml
[footer]
show_api_cost = false
show_subscription_usage = true
```
KeyTypeDefaultNotes
show_api_costbooltrueShow the API cost/tokens line (💰).
show_subscription_usageboolfalseShow 5h/weekly subscription usage (⚡). Claude Code engine only.

When show_subscription_usage is enabled, a compact line like ⚡ 5h: 45% (2h 15m) | 7d: 30% (4d 3h) appears after every Claude Code run. Threshold-based warnings (≥70%) appear regardless of this setting.

preamble

Controls the context preamble injected at the start of every agent prompt.

=== “toml”

```toml
[preamble]
enabled = true
text = "Custom preamble text..."
```
KeyTypeDefaultNotes
enabledbooltrueInject preamble into prompts.
textstring|nullnullCustom preamble text. null uses the built-in default.

The default preamble tells agents they’re running via Telegram, lists key constraints (only assistant text is visible), and requests a structured end-of-task summary.

progress

Controls progress message rendering during agent runs.

=== “toml”

```toml
[progress]
verbosity = "verbose"
max_actions = 8
```
KeyTypeDefaultNotes
verbosity"compact" | "verbose""compact"compact shows status + title only. verbose adds tool detail lines (file paths, commands, patterns).
max_actionsint (0–50)5Maximum action lines shown in the progress message.

Per-chat override: /verbose on and /verbose off override the config default for the current chat without editing the TOML file. /verbose clear removes the override.

cost_budget

=== “toml”

```toml
[cost_budget]
enabled = true
max_cost_per_run = 2.00
max_cost_per_day = 10.00
warn_at_pct = 70
auto_cancel = false
```
KeyTypeDefaultNotes
enabledboolfalseEnable cost budget tracking.
max_cost_per_runfloat|nullnullPer-run cost limit (USD).
max_cost_per_dayfloat|nullnullDaily cost limit (USD).
warn_at_pctint70Warning threshold (0–100).
auto_cancelboolfalseAuto-cancel runs that exceed the per-run limit.

Budget alerts always appear regardless of [footer] settings.

watchdog

=== “toml”

```toml
[watchdog]
liveness_timeout = 600.0
stall_auto_kill = false
stall_repeat_seconds = 180.0
```
KeyTypeDefaultNotes
liveness_timeoutfloat600.0Seconds of no stdout before subprocess.liveness_stall warning (60–3600).
stall_auto_killboolfalseAuto-kill stalled processes. Requires zero TCP + CPU not increasing.
stall_repeat_secondsfloat180.0Interval between repeat stall warnings in Telegram (30–600).

The stall monitor in ProgressEdits fires at 5 min (300s) idle with progressive Telegram notifications. The liveness watchdog in the subprocess layer fires at liveness_timeout with /proc diagnostics. When stall_auto_kill is enabled, auto-kill requires a triple safety gate: timeout exceeded + zero TCP connections + CPU ticks not increasing between snapshots.

Engine-specific config tables

Engines use top-level tables keyed by engine id. Built-in engines are listed here; plugin engines should document their own keys.

codex

KeyTypeDefaultNotes
extra_argsstring[]["-c", "notify=[]"]Extra CLI args for codex (exec-only flags are rejected).
profilestring(unset)Passed as --profile <name> and used as the session title.

=== “untether config”

```sh
untether config set codex.extra_args '["-c", "notify=[]"]'
untether config set codex.profile "work"
```

=== “toml”

```toml
[codex]
extra_args = ["-c", "notify=[]"]
profile = "work"
```

claude

KeyTypeDefaultNotes
modelstring(unset)Optional model override.
allowed_toolsstring[]["Bash", "Read", "Edit", "Write"]Auto-approve tool rules.
dangerously_skip_permissionsboolfalseSkip Claude Code permissions prompts.
use_api_billingboolfalseKeep ANTHROPIC_API_KEY for API billing.

=== “untether config”

```sh
untether config set claude.model "claude-sonnet-4-5-20250929"
untether config set claude.allowed_tools '["Bash", "Read", "Edit", "Write"]'
untether config set claude.dangerously_skip_permissions false
untether config set claude.use_api_billing false
```

=== “toml”

```toml
[claude]
model = "claude-sonnet-4-5-20250929"
allowed_tools = ["Bash", "Read", "Edit", "Write"]
dangerously_skip_permissions = false
use_api_billing = false
```

pi

KeyTypeDefaultNotes
modelstring(unset)Passed as --model.
providerstring(unset)Passed as --provider.
extra_argsstring[][]Extra CLI args for pi.

=== “untether config”

```sh
untether config set pi.model "..."
untether config set pi.provider "..."
untether config set pi.extra_args "[]"
```

=== “toml”

```toml
[pi]
model = "..."
provider = "..."
extra_args = []
```

opencode

KeyTypeDefaultNotes
modelstring(unset)Optional model override.

=== “untether config”

```sh
untether config set opencode.model "claude-sonnet"
```

=== “toml”

```toml
[opencode]
model = "claude-sonnet"
```

gemini

KeyTypeDefaultNotes
modelstring(unset)Optional model override, passed as --model.

=== “untether config”

```sh
untether config set gemini.model "gemini-2.5-pro"
```

=== “toml”

```toml
[gemini]
model = "gemini-2.5-pro"
```

Approval mode Gemini CLI’s approval mode (read-only vs full access) is toggled per chat via /configApproval mode, not the config file. See inline settings.

amp

KeyTypeDefaultNotes
modestring(unset)Execution mode, passed as --mode. Values: deep, free, rush, smart.
modelstring(unset)Display label shown in the message footer. Overridden by mode if both are set.
dangerously_allow_allbooltruePass --dangerously-allow-all to skip permission prompts.
stream_json_inputboolfalsePass --stream-json-input for stdin-based prompt delivery.

=== “untether config”

```sh
untether config set amp.mode "deep"
untether config set amp.dangerously_allow_all true
```

=== “toml”

```toml
[amp]
mode = "deep"
dangerously_allow_all = true
```

Triggers

Webhook and cron triggers that start agent runs from external events. See the full Triggers reference for auth, templating, and routing details.

=== “toml”

```toml
[triggers]
enabled = true

[triggers.server]
host = "127.0.0.1"
port = 9876
rate_limit = 60
max_body_bytes = 1_048_576

[[triggers.webhooks]]
id = "github-push"
path = "/hooks/github"
project = "myapp"
engine = "claude"
auth = "hmac-sha256"
secret = "whsec_abc..."
prompt_template = "Review push to {{ref}} by {{pusher.name}}"

[[triggers.crons]]
id = "daily-review"
schedule = "0 9 * * 1-5"
project = "myapp"
engine = "claude"
prompt = "Review open PRs and summarise status."
```

[triggers]

KeyTypeDefaultNotes
enabledboolfalseMaster switch. No server or cron loop starts when false.

[triggers.server]

KeyTypeDefaultNotes
hoststring"127.0.0.1"Bind address. Use a reverse proxy for internet exposure.
portint9876Listen port (1—65535).
rate_limitint60Max requests per minute (global + per-webhook).
max_body_bytesint1048576Max request body size in bytes (1 KB—10 MB).

[[triggers.webhooks]]

KeyTypeDefaultNotes
idstring(required)Unique identifier.
pathstring(required)URL path (e.g. /hooks/github).
projectstring|nullnullProject alias for working directory.
enginestring|nullnullEngine override.
chat_idint|nullnullTelegram chat. Falls back to transport default.
authstring"bearer""bearer", "hmac-sha256", "hmac-sha1", or "none".
secretstring|nullnullAuth secret. Required when auth is not "none".
prompt_templatestring(required)Prompt with {{field.path}} substitutions.
event_filterstring|nullnullOnly process matching event type headers.

[[triggers.crons]]

KeyTypeDefaultNotes
idstring(required)Unique identifier.
schedulestring(required)5-field cron expression.
projectstring|nullnullProject alias for working directory.
enginestring|nullnullEngine override.
chat_idint|nullnullTelegram chat. Falls back to transport default.
promptstring(required)Prompt sent to the engine.
Was this helpful?

Related Articles