config.toml
Path: <repo-root>/.kasmos/config.toml
This is the project-local configuration file. It is the authoritative source for agent profiles, lifecycle phase mappings, UI behavior, orchestration tuning, and webhook hooks.
kasmos generates this file on first boot via kas setup. You can safely edit it by hand — it is re-read on each startup.
top-level fields
| field | type | default | description |
|---|---|---|---|
default_program | string | codex when generated; auto-detected fallback (codex → opencode → claude) | fallback agent executable when a role has no profile |
auto_yes | bool | true | when true, the daemon automatically accepts all agent prompts |
daemon_poll_interval | int (ms) | 1000 | how often the daemon checks session state (milliseconds) |
branch_prefix | string | <username>/ | prefix prepended to git branch names created by kasmos |
notifications_enabled | bool? | true | desktop notifications; null defaults to enabled |
claude_no_flicker | bool? | false | set CLAUDE_CODE_NO_FLICKER=1 on spawned claude agents; reduces TUI flicker but may affect prompt detection |
database_url | string | — | remote task store URL (e.g. http://host:7433); local SQLite used when empty |
[phases] — lifecycle phase-to-role mapping
Maps lifecycle phase names to agent role names. kasmos uses this to look up which [agents.<role>] profile to use for each phase.
[phases]
planning = "planner"
elaborating = "architect"
implementing = "coder"
spec_review = "reviewer"
quality_review = "reviewer"
fixer = "fixer"
readiness_review = "master" # also accepted as master_review (backward-compat alias)
The phase names on the left are fixed lifecycle identifiers. The role names on the right must match keys in the [agents] table.
readiness_reviewvsmaster_review:readiness_reviewis the canonical phase key — it maps to the master agent role that runs during theverifyingFSM status. The old keymaster_reviewis accepted as a backward-compatible alias and is resolved toreadiness_reviewat profile resolution time (not at load time). New configs should usereadiness_review.
[agents.<role>] — agent profiles
Each role is configured in its own [agents.<role>] subsection. See the agent profiles reference for all available fields.
[agents.coder]
enabled = true
program = "codex"
model = "gpt-5.5"
execution_mode = "tmux"
tier = "fast"
effort = "low"
[agents.planner_opus]
enabled = true
program = "claude"
model = "claude-opus-4-8"
execution_mode = "tmux"
effort = "xhigh"
flags = ["--permission-mode bypassPermissions"]
permission_default can be set per profile to "prompt" or "bypass", or left unset to inherit the spawn-source default. "inherit" is accepted as an alias for unset, but normalized configs usually omit it; see agent profiles: permission_default for resolution details.
[ui] — UI behavior
| field | type | default | description |
|---|---|---|---|
animate_banner | bool | false | enable idle banner animation |
auto_advance | bool? | true | skip the planner→architect confirmation dialog; start elaboration automatically after planning finishes |
auto_advance_waves | bool? | true | skip confirmation dialog after a clean wave |
auto_review_fix | bool? | true | automatically start the review→fix→re-review loop |
max_review_fix_cycles | int? | 0 (unlimited) | cap the review-fix loop iterations; 0 means no cap |
auto_readiness_review | bool? | true | when true, reviewer approval transitions to verifying and spawns an independent master review. Master approval binds both task HEAD and default-branch SHA. When false, approval flows directly to done with verified_by = "auto". All done approvals become stale after either bound ref moves. |
auto_create_pr | bool? | true | create or adopt a GitHub pull request after terminal approval |
readiness_self_fix_max_lines | int? | 80 | maximum number of net lines the master agent may change in a single self-fix attempt; findings that would require more are escalated via verify_failed instead |
readiness_max_verify_cycles | int? | 2 | deprecated compatibility setting. It is still parsed, but never changes verify_failed into approval. Use max_review_fix_cycles to bound automatic fixer loops. |
theme_source | string | "static" | color palette source: empty/"static" uses the built-in Rose Pine Moon palette; "system" resolves a Linux system-derived palette |
system_theme_provider | string | "auto" | provider used when theme_source = "system": empty/"auto", "file", "caelestia", "freedesktop", or "gnome" |
theme_palette_file | string | — | palette JSON file used by the "file" and "caelestia" providers; ~ is expanded, and relative paths resolve from .kasmos/config.toml's directory |
[ui]
animate_banner = false
auto_advance = true
auto_advance_waves = true
auto_review_fix = true
max_review_fix_cycles = 3
auto_readiness_review = true # set to false to skip verifying status and go directly to done
auto_create_pr = true # create or adopt a PR after terminal approval
readiness_self_fix_max_lines = 80 # master agent self-fix ceiling (net lines changed)
readiness_max_verify_cycles = 2 # deprecated compatibility setting
system theme example:
[ui]
theme_source = "system"
system_theme_provider = "file"
theme_palette_file = "~/.config/caelestia/kasmos-theme.json"
Omitting all three theme keys keeps the built-in Rose Pine Moon palette. Setting theme_source = "" or theme_source = "static" also disables system theming. When theme_source = "system" and system_theme_provider is empty or "auto", kasmos uses the Linux desktop color-scheme preference when it can and safely falls back to the built-in palette when it cannot. System theme providers are Linux-only; unsupported platforms, unreadable files, unknown providers, and invalid palette JSON all fall back to Rose Pine Moon.
The "file" and "caelestia" providers read a JSON palette with kasmos semantic role names. A top-level object is supported, and so is a nested colors or colours object. Missing roles inherit the default palette.
{
"base": "#232136",
"surface": "#2a273f",
"overlay": "#393552",
"text": "#e0def4",
"muted": "#6e6a86",
"subtle": "#908caa",
"iris": "#c4a7e7",
"foam": "#9ccfd8",
"gold": "#f6c177"
}
For Caelestia, configure Caelestia to generate a kasmos-specific palette/template target at the file path you set in theme_palette_file; the path above is only an example. Relative paths are resolved from the directory containing .kasmos/config.toml, so theme_palette_file = "themes/kasmos-theme.json" points at .kasmos/themes/kasmos-theme.json. Use system_theme_provider = "file" for a plain generated JSON file, or system_theme_provider = "caelestia" when you want the config to document that Caelestia owns the generated target. kasmos resolves the palette on startup only, so restart kasmos after regenerating the file.
[sdk] — SDK transcript retention
Controls the in-process memory limits for SDK-backed agent sessions (those with execution_mode = "sdk"). Only the in-process renderer is affected — the on-disk log file and tmux scrollback are not.
| field | type | default | description |
|---|---|---|---|
transcript_max_bytes | int64 | 4194304 (4 MiB) | byte cap across flat lines and structured turns combined |
transcript_max_turns | int64 | 2000 | completed-turn cap; the active in-progress turn is never evicted |
value semantics:
- key omitted — use the built-in default.
0— disable that dimension's limit (unlimited).- negative — normalized to
0(unlimited).
[sdk]
transcript_max_bytes = 4194304 # 4 MiB (default; may be omitted)
transcript_max_turns = 2000 # (default; may be omitted)
To disable retention limits entirely (unlimited growth):
[sdk]
transcript_max_bytes = 0
transcript_max_turns = 0
When the byte limit is exceeded the renderer evicts the oldest completed turns first (FIFO), then trims the oldest flat lines, and finally truncates rows from the active turn as a last resort. Eviction markers appear in rendered output: earlier turns evicted: N in the structured presentation and [earlier lines evicted: N] in flat CapturePaneContent output.
See sdk backend: transcript retention for the full eviction model.
[telemetry] — crash reporting
| field | type | default | description |
|---|---|---|---|
enabled | bool? | true | Sentry crash reporting; null defaults to enabled |
[telemetry]
enabled = false
[resources] — agent resource controls
Controls the CPU and I/O priority of kasmos-managed agent processes. The block is entirely optional — omitting it leaves default scheduling unchanged.
| field | type | default | description |
|---|---|---|---|
profile | string | "normal" | preset to apply: normal (no-op), interactive, or custom |
nice | int | (profile preset) | process niceness value: 0–19 (higher = lower priority). Negative values are rejected. |
ionice_class | string | (profile preset) | Linux I/O scheduling class: none, best-effort, or idle. realtime is rejected. |
ionice_level | int | (profile preset) | I/O priority level 0–7; only valid for ionice_class = "best-effort" |
build_jobs | int | (profile preset) | -j parallelism hint injected into build tool environment; 0 = unset |
go_package_parallelism | int | (profile preset) | sets GOFLAGS=-p=<n> for go build/go test; 0 = unset |
gomaxprocs | int | (profile preset) | overrides GOMAXPROCS for agent processes; 0 = unset |
max_parallel_wave_tasks | int | (profile preset) | caps concurrent wave tasks; 0 = unset/unlimited |
[resources.env] | table | — | extra environment variables injected into every agent process; keys must be valid shell names and must not overwrite kasmos control variables |
profiles:
normal(default) — no wrapper, no environment injection, no wave cap. Identical to omitting the block.interactive— preset for latency-sensitive desktop workloads:nice = 10,ionice_class = "best-effort",ionice_level = 7,build_jobs = 1,go_package_parallelism = 1,gomaxprocs = 2,max_parallel_wave_tasks = 1. Individual keys override the preset.custom— starts fromnormaland enables only the keys you supply explicitly. At least one control key must be set.
minimal example (interactive preset):
[resources]
profile = "interactive"
# optional overrides
build_jobs = 1
go_package_parallelism = 1
max_parallel_wave_tasks = 1
custom example:
[resources]
profile = "custom"
nice = 15
max_parallel_wave_tasks = 2
See the interactive resource controls guide for a step-by-step walkthrough including how to confirm the profile is active and how pending wave tasks behave.
[orchestration] — wave orchestration tuning
| field | type | default | description |
|---|---|---|---|
blueprint_skip_threshold | int? | 2 | when a plan has ≤ this many tasks, kasmos skips elaboration and wave orchestration and runs a single-agent "blueprint" mode instead |
planners | []string? | ["planner_opus", "planner_gpt"] in generated configs | ordered [agents.<profile>] names to run as parallel planners during plan_start; unset or [] keeps the legacy single-planner path |
[orchestration]
blueprint_skip_threshold = 2
planners = ["planner_opus", "planner_gpt"]
Setting this to 0 forces multi-agent wave orchestration for every plan, including single-task ones. Setting it higher causes more plans to run in single-agent mode.
To run multiple planners, list the agent profiles in the order they should run:
[orchestration]
planners = ["planner_opus", "planner_gpt"]
[agents.planner_opus]
enabled = true
program = "claude"
model = "claude-opus-4-8"
[agents.planner_gpt]
enabled = true
program = "codex"
model = "gpt-5.5"
When planners is unset or [], kasmos keeps the legacy path: the configured planning profile writes the task store and emits planner_finished.
When planners contains profile names, the runtime sequence is:
plan_startclears stale.kasmos/cache/<planSlug>-planner-<profile>.mdfiles- kasmos spawns one planner per listed profile in draft mode
- the first listed planner also updates the task store as a preview
- each draft-mode planner writes its cache file and emits gateway-only
planner_draft_finishedwith{"planner_id":"<profile>"} - the processor aggregates draft signals and internally advances planning when every expected profile has reported
- the final architect reads all planner draft caches, derives its own implementation baseline inline, writes the final task-store plan, and records consumed drafts in
decision_audit.planner_drafts
parallel_planner_architect is a legacy key. If it remains in an older config, kasmos ignores it and logs a warning; use [orchestration].planners for current multi-planner behavior.
Blueprint skip still applies after the planner draft is available. A small planner draft can skip the final architect pass, so planner draft caches may be produced but unused.
[linear.receipts] — Linear receipt comments
Posts compact one-way comments from kasmos to explicitly linked Linear issues. See the linear receipts guide for setup and examples.
[linear.receipts]
enabled = true
events = ["implement_start", "implement_finished", "review_approved"]
pr = true
merge = true
cancel = true
[linear.receipts.state_map]
implementing = "lin_state_in_progress"
reviewing = "lin_state_in_review"
done = "lin_state_done"
cancelled = "lin_state_cancelled"
| field | type | default | description |
|---|---|---|---|
enabled | bool | false | enable Linear receipt comments for tasks with explicit Linear links |
events | []string | ["plan_start", "implement_start", "implement_finished", "review_approved", "review_changes_requested", "verify_approved", "verify_failed", "mark_done", "cancel", "reopen"] | lifecycle FSM events that post status receipts; omit or set empty to use the default allowlist |
pr | bool? | true | post a PR receipt after a PR URL is persisted |
merge | bool? | true | post a merge receipt when kasmos merges a task branch |
cancel | bool? | true | post a cancellation receipt when kasmos cancels a task |
[linear.receipts.state_map] | table | — | optional map from kasmos status (ready, planning, implementing, reviewing, verifying, done, cancelled) to Linear workflow state id |
receipt failures never block kasmos lifecycle transitions. missing Linear API credentials are silent; other Linear errors are recorded as EventTaskLinearReceiptFailed audit rows.
[linear.triggers] — guarded Linear-side triggers
Polls Linear for strict /kasmos <verb> comments and configured trigger labels, then routes accepted work through kasmos link/create/FSM paths. See the linear triggers guide for operator setup and troubleshooting.
[linear.triggers]
enabled = true
poll_interval = "1m"
lookback = "15m"
max_issues_per_poll = 100
verbs = ["help", "status", "link", "create", "plan", "start"]
ack_comment_body = "kasmos trigger ack"
[[linear.triggers.routes]]
team_id = "6f24259a-1f9f-4c5b-9f14-9dff1f6b2b21"
project_id = "a82f4c97-3e15-47ef-9a7a-2b04f4e2a3dd"
require_labels = ["4d1a5ec7-3e1d-4f2a-9a66-8a5e64f0d721"]
topic = "linear"
branch_prefix = "linear/"
[linear.triggers.labels]
create = "0a5bf2d7-b581-4183-9e28-3fef63d47a18"
plan = "c9170d93-68cf-4370-a7ea-6503a1dd30be"
start = "89c8c2bf-d118-42e0-8f3d-85eeb7d775db"
ack = "a8a63a51-5042-4697-a5dd-6a3e342fffd7"
[linear.triggers.actor]
allowed_user_ids = ["a7c74ca9-8a18-4f62-a784-76ec43a5cb87"]
allowed_user_emails = ["ops@example.com"]
allow_public_status = true
[linear.triggers.start_guard]
require_start_label = true
allow_label_start = false
[linear.triggers.webhook]
enabled = true
secret_env = "KASMOS_LINEAR_WEBHOOK_SECRET"
timestamp_tolerance = "5m"
max_body_bytes = 1048576
| field | type | default | validation |
|---|---|---|---|
enabled | bool | false | when false, the zero-value trigger config is used and no trigger routes are required |
poll_interval | duration | "1m" | values below "15s" are clamped to "15s"; zero or omitted uses the default |
lookback | duration | "15m" | zero or negative values use the default first-poll comment lookback |
max_issues_per_poll | int | 100 | values <= 0 use the default |
verbs | []string | ["help", "status", "link", "create", "plan", "start"] | each value must be one of help, status, link, create, plan, or start; mutating verbs require an actor allowlist |
ack_comment_body | string | "kasmos trigger ack" | empty uses the default acknowledgement comment body |
[[linear.triggers.routes]] | table array | — | at least one route is required when enabled; duplicate routes by team_id, project_id, and sorted require_labels are rejected |
team_id | string | — | required Linear team UUID for each route |
project_id | string | "" | optional Linear project UUID route qualifier |
require_labels | []string | [] | optional Linear label UUIDs; all listed labels must be present on the issue |
topic | string | — | required kasmos topic for tasks created by the route |
branch_prefix | string | "" | optional branch prefix appended before the generated task filename |
[linear.triggers.labels] | table | all empty | optional UUID map for label-source triggers and acknowledgements |
create | string | "" | Linear label UUID that enqueues create intents |
plan | string | "" | Linear label UUID that enqueues create-and-plan intents |
start | string | "" | Linear label UUID used by start guards and optional label-only start |
ack | string | "" | optional Linear label UUID applied after successful create or plan label triggers |
[linear.triggers.actor] | table | empty allowlist | required for any enabled mutating verb |
allowed_user_ids | []string | [] | canonical Linear User.id allowlist for mutating comment commands |
allowed_user_emails | []string | [] | friendly aliases resolved to Linear users on first hit and cached |
allow_public_status | bool | false | when true, /kasmos status and /kasmos help can bypass the actor allowlist |
[linear.triggers.start_guard] | table | both false | optional guard settings for start triggers |
require_start_label | bool | false | when true, /kasmos start requires the issue to carry labels.start |
allow_label_start | bool | false | when true, label-only start is allowed; validation requires labels.start |
[linear.triggers.webhook] | table | disabled | optional inbound linear webhook ingestion settings |
enabled | bool | false | when true, kas serve accepts signed linear webhook deliveries for the project endpoint |
secret_env | string | KASMOS_LINEAR_WEBHOOK_SECRET | environment variable that contains the linear webhook signing secret |
timestamp_tolerance | duration | "5m" | accepted delivery timestamp skew; clamped from "1m" to "15m" |
max_body_bytes | int64 | 1048576 | maximum raw request body size before the webhook returns body_too_large |
linear webhook delivery ids are part of the ingestion contract, not optional metadata. requests missing Linear-Delivery are rejected with missing_delivery before kasmos records a delivery row or evaluates duplicate state, so a proxy/header issue cannot collapse unrelated deliveries onto an empty dedupe key. if a delivery was recorded but processing stopped in received or failed, a retry with the same delivery id resumes normalization and enqueue instead of being treated as a terminal duplicate. after accepted deliveries enqueue trigger rows, kas serve drains the queue until no rows remain or a retryable error stops the pass.
[[hooks]] — FSM transition hooks
Hooks fire when a task transitions between lifecycle states. They are defined as an array of tables.
webhook hook
[[hooks]]
type = "webhook"
url = "https://your-server.example.com/webhook"
events = ["implement_start", "implement_finished", "review_approved"]
[hooks.headers]
Authorization = "Bearer your-secret-token"
X-Source = "kasmos"
command hook
[[hooks]]
type = "command"
command = "/usr/local/bin/notify-team.sh"
events = ["review_approved"]
hook fields
| field | type | description |
|---|---|---|
type | string | "webhook" or "command" |
url | string | (webhook) full URL to POST to |
headers | table | (webhook) additional HTTP headers |
command | string | (command) shell command to execute |
events | []string | FSM event names that trigger this hook |
Valid event names: plan_start, planner_finished, implement_start, implement_finished, request_review, review_approved, review_changes_requested, start_over, reimplement, cancel, reopen.
full example
# Generated by kas setup
default_program = "codex"
auto_yes = true
daemon_poll_interval = 1000
branch_prefix = "alice/"
notifications_enabled = true
claude_no_flicker = false
[phases]
planning = "planner"
elaborating = "architect"
implementing = "coder"
spec_review = "reviewer"
quality_review = "reviewer"
fixer = "fixer"
readiness_review = "master"
[agents.planner_opus]
enabled = true
program = "claude"
model = "claude-opus-4-8"
temperature = 0.3
execution_mode = "tmux"
tier = "fast"
effort = "xhigh"
flags = ["--permission-mode bypassPermissions"]
[agents.planner_gpt]
enabled = true
program = "codex"
model = "gpt-5.5"
temperature = 0.3
execution_mode = "tmux"
tier = "fast"
effort = "xhigh"
[agents.architect]
enabled = true
program = "claude"
model = "claude-opus-4-8"
temperature = 0.2
execution_mode = "tmux"
tier = "fast"
effort = "xhigh"
flags = ["--permission-mode bypassPermissions"]
[agents.coder]
enabled = true
program = "codex"
model = "gpt-5.5"
temperature = 0.1
execution_mode = "tmux"
tier = "fast"
effort = "low"
[agents.reviewer]
enabled = true
program = "claude"
model = "claude-sonnet-4-6"
temperature = 0.2
execution_mode = "tmux"
tier = "fast"
effort = "medium"
flags = ["--permission-mode bypassPermissions"]
[agents.master]
enabled = true
program = "codex"
model = "gpt-5.5"
temperature = 0.2
execution_mode = "tmux"
tier = "fast"
effort = "xhigh"
[agents.fixer]
enabled = true
program = "codex"
model = "gpt-5.5"
temperature = 0.1
execution_mode = "tmux"
tier = "fast"
effort = "high"
[ui]
animate_banner = false
auto_advance = true
auto_advance_waves = true
auto_review_fix = true
max_review_fix_cycles = 3
auto_readiness_review = true
readiness_self_fix_max_lines = 80
readiness_max_verify_cycles = 2
theme_source = "system"
system_theme_provider = "file"
theme_palette_file = "~/.config/caelestia/kasmos-theme.json"
[telemetry]
enabled = true
[orchestration]
blueprint_skip_threshold = 2
planners = ["planner_opus", "planner_gpt"]
[sdk]
transcript_max_bytes = 4194304 # 4 MiB per sdk session renderer
transcript_max_turns = 2000 # completed structured turns retained
[[hooks]]
type = "webhook"
url = "https://hooks.example.com/kasmos"
events = ["implement_finished", "review_approved"]
troubleshooting
If kasmos does not seem to pick up your config changes, run:
kas debug
This prints the resolved config path and the fully-parsed configuration, so you can confirm the right file is being read.