Repository navigation
Expand file tree
/
Copy pathconfig.example.yaml
More file actions
794 lines (744 loc) · 46.1 KB
/
Copy pathconfig.example.yaml
File metadata and controls
794 lines (744 loc) · 46.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
# yaml-language-server: $schema=https://coddy.dev/config.schema.json
# Full field reference: docs/reference/config.md
# .env file support
# If $CODDY_HOME/.env exists, it is loaded at startup before config.yaml is parsed.
# Variables already set in the process environment are NOT overridden — .env is a fallback only.
# Use it to keep secrets out of config.yaml without touching your shell profile:
#
# ~/.coddy/.env
# ──────────────
# OPENAI_API_KEY=sk-...
# TELEGRAM_BOT_TOKEN=8992982910:AAF...
# ANTHROPIC_API_KEY=sk-ant-...
#
# Supported syntax: KEY=value, export KEY=value, "quoted values", # comments.
# api_key must be the provider secret string (often starts with sk-...), a "${ENV}" reference, or empty to use NAME_API_KEY at runtime; never a web page URL.
providers:
- name: openai
type: openai
api_key: "${OPENAI_API_KEY}"
# Optional credential helper: when api_key is empty, this command is run via the
# shell and its trimmed stdout is used as the key (like git/docker credential
# helpers or AWS credential_process). Lets a provider fetch short-lived or
# login-issued keys without storing a static secret. Falls back to NAME_API_KEY.
# api_key_command: "my-cli print-token"
# Route of every request of this provider (completions, model list, usage, sign-in).
# inherit (the default, same as leaving it out) follows HTTPS_PROXY, HTTP_PROXY and
# NO_PROXY of the Coddy process; none connects directly and ignores them; a URL
# (http, https, socks5, socks5h) sends every request of this row through that proxy.
# proxy: none
# proxy: "http://127.0.0.1:8888"
# proxy: "socks5h://127.0.0.1:1080"
# Optional bound on each LLM HTTP request, including the streamed body read.
# 0 (default) sets no client timeout; raise it only if you want a hard cap.
# timeout_ms: 300000
# Local Ollama or llama.cpp server (OpenAI-compatible)
# - name: local
# type: openai
# api_base: "http://localhost:11434/v1"
# api_key: "~"
# Anthropic
# - name: anthropic
# type: anthropic
# api_key: "${ANTHROPIC_API_KEY}"
# NeuralDeep serves the same OpenAI-compatible API from two deployments:
# https://api.neuraldeep.ru/v1 (Russia, used when api_base is empty) and
# https://api.neuraldeep.tech/v1 (the mirror for everywhere else). api_base
# picks one - any other value falls back to the first - and the choice also
# decides which hub signs you in.
# Instead of pasting a key, sign in with your hub account: `coddy providers
# login neuraldeep` (browser flow; --device for headless, --api-base to pick
# the deployment) or Sign In with NeuralDeep in the bundled web UI. The key
# lands under $CODDY_HOME/providers/<name>/neuraldeep-auth.json; an explicit
# api_key, api_key_command, or NEURALDEEP_API_KEY env var still wins when set.
# - name: neuraldeep
# type: neuraldeep
# # api_base: "https://api.neuraldeep.tech/v1" # outside Russia
# api_key: "${NEURALDEEP_API_KEY}"
# # The account usage panel (the console footer line and /usage, the usage
# # section and banner in the web UI) is on by default and reads the hub's
# # GET /v1/limits. false hides it and stops those reads for this row.
# # usage_limits_panel: false
# Codex ChatGPT OAuth. In the bundled web UI, add/select this provider and use
# Sign In with ChatGPT. Coddy stores tokens under $CODDY_HOME/providers/codex/
# with restrictive file permissions (no api_key or api_base needed). A Codex CLI
# login remains a compatibility fallback when no Coddy-managed token exists.
# The account usage panel (the console footer line and /usage, the usage
# section and banner in the web UI) is on by default and reads the Codex
# backend's usage endpoint. false hides it and stops those reads for this row.
# - name: codex
# type: codex
# # usage_limits_panel: false
# Devin (Cognition) account. `coddy providers login devin` signs in through
# the browser (or reuses `devin auth login` with --devin-cli), stores the
# session token under $CODDY_HOME/providers/devin/ and adds this row plus one
# model per family. api_base is ignored; an api_key here (a Devin session
# token) wins over both logins.
# - name: devin
# type: devin
# # usage_limits_panel: false # hides the account usage panel and stops its reads
# OpenCode Go / Console gateway: one API key, its curated coding models served
# over the right wire protocol per model id (Grok/GPT/Muse on the Responses
# API, Gemini on Google, Claude/Qwen/MiniMax M3 on Anthropic for Go, the rest
# OpenAI-compatible; Console's Qwen3.8 Max and MiniMax use Chat Completions).
# Get a key at https://opencode.ai/console, then either paste it here or run
# `coddy providers login opencode` (a device-code account sign-in; --api-key -
# reads a service account key instead). The login also writes this row and
# the gateway's models. api_base picks the gateway: omit it for Go
# (the default) or set https://opencode.ai/inference/v1 for pay-as-you-go
# Console. An explicit api_key, api_key_command or OPENCODE_API_KEY env var
# wins over the stored login.
# - name: opencode
# type: opencode
# # api_base: "https://opencode.ai/inference/v1" # Console instead of Go
# api_key: "${OPENCODE_API_KEY}"
# A local agent that speaks the Agent Client Protocol on stdio, served as a
# model: it runs its own tools in the session's workspace and asks you
# before it acts (acp.permissions). Its models are <name>/default (the
# agent's own) or one of the models it offers. See
# https://coddy.dev/docs/features/acp-provider.
# - name: claude
# type: acp
# acp:
# command: claude-agent-acp # opencode / coddy take args: [acp]
# modes: {plan: plan, ask: plan} # read-only modes for plan and ask (codex-acp: read-only)
# # auth_method: api-key # codex-acp with OPENAI_API_KEY in env
# # env: {OPENAI_API_KEY: "${OPENAI_API_KEY}"}
# # process: per_session # zcode-acp-server: one prompt at a time per process
# OpenAI-compatible (DeepSeek, Groq, Together, etc.)
# Use type openai and include /v1 in api_base.
# - name: deepseek
# type: openai
# api_base: "https://api.deepseek.com/v1"
# api_key: "${DEEPSEEK_API_KEY}"
models:
- model: "openai/gpt-5.6-terra"
max_tokens: 8192
reasoning_default: medium # a reasoning model takes a level, not a temperature
multimodal: true # model accepts image/file inputs; UI shows file attachment button; read shows it image files
# Reasoning models expose a reasoning-level selector in the composer.
# Levels are auto-detected from the model id (gpt-5*/gpt-6* -> minimal,low,medium,high;
# o-series, gpt-oss*, qwen3* — qwen3, qwen3.5, qwen3.6, ... — and Claude thinking
# models -> low,medium,high). Override or disable below.
# - model: "openai/gpt-5"
# max_tokens: 8192
# reasoning_default: medium # level pre-selected for new chats (must be a valid level)
# # reasoning_levels: [low, high] # optional: override the offered levels; [] hides the selector; omit the key to auto-detect from the model id
# # allow_reasoning_off: true # optional: add Off only after verifying this deployment honours Coddy's disable-reasoning request
# - model: "claude/default" # an acp row: the agent's own model
# - model: "devin/claude-sonnet-5" # one entry per Devin family; each level is a variant
# reasoning_levels: [low, medium, high, xhigh, max]
# reasoning_default: medium
# - model: "codex/gpt-5.6-sol" # provider name / Codex model slug (fetch after Sign In)
# max_tokens: 8192 # retained in config; Codex does not accept max_output_tokens
# reasoning_default: medium
# NeuralDeep examples (reasoning levels auto-detect to low, medium, high).
# - model: "neuraldeep/qwen3.8-27b"
# max_tokens: 8192
# temperature: 0.2
# multimodal: false
# # allow_reasoning_off: true # NeuralDeep Hub may ignore this request; use only after verifying the deployment
# - model: "neuraldeep/qwen3.6-35b-a3b"
# max_tokens: 8192 # qwen3 reasoning also pins chat_template_kwargs.enable_thinking
# multimodal: true
# Servers and proxies that handle SSE badly can be talked to without streaming:
# one blocking request, whole answer at once. Nothing appears until the model is
# done, and Stop during the call loses the answer. Not available for codex models.
# - model: "local/qwen3-30b"
# max_tokens: 8192
# stream: false
# A small-window local model is not sent the schemas of tools it never calls:
# it names a toolset (below), and a session can pick another with /toolset.
# - model: "local/qwen3.6-35b"
# max_tokens: 4096
# max_context_tokens: 49152
# toolset: coding
# Named presets of the tools a session is offered. tools is an allowlist of tool
# names, * or a prefix* (context7__* is every tool of that MCP server); empty or
# absent offers every tool the mode would. This one list takes the request's tool
# definitions from about 11,700 tokens to about 3,800. disallowed_tools removes
# names after it, with the same syntax. Both only narrow: a tool the mode or a
# subagent definition hides stays hidden, and a call to a tool the session was
# not offered is refused. A model without a toolset keeps every tool, and
# /toolset default does the same for one session. coddy -t warns about a name
# that matches no tool.
# toolsets:
# - name: coding
# tools: [read, write, edit, apply_patch, glob, grep, print_tree, keep_result, run_command,
# background_output, background_wait, background_stop, compact_context]
# - name: no-remote
# disallowed_tools: [ssh_run_command, http_request] # everything except these
agent:
model: "openai/gpt-5.6-terra"
max_turns: 165 # ReAct steps per prompt; set 0 to disable the limit
# queue_mode: steer # Enter during a turn: steer or after_turn; unset asks on first use
# LLM retry and pacing. Transport retries and consecutive empty-answer,
# first-token or output-limit recoveries (a step cut off at max_tokens) share
# llm_retry_max extra attempts per model step.
# Tool progress starts a fresh allowance; recovery also uses max_turns.
# For 429/5xx the pause is Retry-After when present, the exponential backoff from
# llm_retry_base_ms otherwise. llm_min_interval_ms spaces out ALL calls,
# retries included - the working knob on strict free tiers (e.g. 6 req/min).
# llm_retry_max: 3 # shared extra attempts (0 disables retries and no-answer recovery)
# llm_retry_base_ms: 1000 # initial backoff between retries
# llm_min_interval_ms: 0 # min gap between LLM calls; e.g. 12000 on strict free tiers
# llm_first_token_timeout_ms: 90000 # cancel a silent streamed call after this long (0 disables)
# llm_stream_idle_timeout_ms: 300000 # cut a streamed answer that goes silent mid-way for this long (0 disables)
# Runaway-loop protection. Cuts a streamed response that degenerates into
# repeating the same passage, and blocks a tool requested over and over with
# identical arguments. The model is nudged back on track first; only a turn
# that keeps looping is stopped with a notice.
# loop_guard: true
# loop_tool_repeat_limit: 2 # identical calls in successive ReAct responses before intervening (0 disables)
# loop_stream_repeat_cycles: 5 # identical output cycles in one stream before it is cut (0 disables)
# loop_nudge_max: 1 # nudges before the turn is stopped
# Auto-continue when a usage limit resets (Claude Desktop style). A provider
# pause the retries could never cover (a 429 naming a reset far ahead) ends
# the turn with the error by default; with the wait on, a top-level turn
# waits for the reset, tells its client "Usage limit reached · resuming at
# 20:59" every 20 s, and then re-issues the same call. The turn lock and
# the client stream stay open meanwhile, hence off by default.
# wait_for_limit_reset: false
# wait_for_limit_reset_max_ms: 14400000 # total per turn (4 h), the retry wrapper's sleeps on a limit
# # included; under 60 s it also bounds ordinary 429 retries;
# # a longer pause ends the turn; 0 never waits
# Session goal and supervisor: /goal <objective> starts work on a goal, and a
# second model checks every turn against it, continues unfinished work and
# hands the goal back to you when it needs you. No key is needed for that:
# without this section, or with "supervisor: {}", the session's own model
# checks the work. /goal --model <id> --reasoning <level> picks the checker of
# one goal. enable also checks ordinary turns against the latest request.
# supervisor:
# enable: false
# model: "" # the checking model; empty uses the session's (another family judges best)
# verify: true # a read-only subagent confirms a met goal against the files
# max_continuations: 10 # automatic turns per goal, then one wrap-up turn
# token_budget: 0 # uncached input + output tokens per goal; 0 = no cap
# max_chain_sessions: 10 # sessions a goal chain (/goal --then-file) opens; 0 = chains off
# stall_seconds: 300 # 0 disables the stall timer
# max_nudges: 2 # recovery turns after a stall, a loop or a failed turn
# loop_repeat: 3 # repeated identical tool operations; 0 disables
sessions:
# Directory to store session bundles.
# Leave empty to use default $CODDY_HOME/sessions.
dir: ""
# Context compaction: summarize older history so long sessions keep fitting the
# model context window. Manual /compact command plus automatic trigger.
# compaction:
# enable: true # master switch (manual command and automation)
# auto_enable: true # automatic compaction: the threshold trigger, the fold of a long turn's
# # earlier steps behind its prompt, and one compaction and retry after a
# # request the provider refused as too large; false keeps manual /compact
# threshold_percent: 80 # auto-compact at this percent of the model's context window (1..100)
# keep_recent_turns: 2 # last N user turns stay verbatim; 0 summarizes everything
# model: "" # optional models[].model for the summarizer; empty = session model
# fallback_models: [] # tried in order when the summarizer above them fails; the session
# # model is the last resort whether or not it is listed
# # Collapse superseded tool results to placeholders in the LLM request (the
# # persisted transcript stays complete). read/grep results survive while marked
# # or recent; glob, print_tree, websearch and webfetch results survive in the
# # latest three steps that hold one and, for glob and print_tree, until a write
# # lands under the folder they listed.
# result_eviction:
# enable: true # master switch
# keep_recent: 2 # most recent evictable read/grep results kept as a working window; 0 keeps none
# min_result_bytes: 2000 # results this size or smaller are never evicted
# start_percent: 50 # wait until the context is this full before evicting; 0 = from the
# # first result. A placeholder written into the middle of the history
# # invalidates every token the provider cached behind it.
prompts:
# Directory containing prompt templates (Go text/template).
# Leave empty to use built-in templates. Paths support ~ and ${CWD} (session cwd at render time).
# Template fields: {{.CWD}}, {{.Tools}}, {{.Skills}}, {{.Rules}} (the AGENTS.md and DESIGN.md of
# the agent home and the workspace, then the always-on rules), {{.Instructions}} (the files of
# instructions.files), {{.TodoList}}, {{.Memory}}, {{.UTCNow}}
# (use {{if .TodoList}} ... {{end}} for optional checklist injection; see docs/getting-started/configuration.md)
# Coddy prepends its identity line ("You are Coddy, ...") to any template that does not
# already open with it, so gateways can attribute the traffic. See internal/prompts/identity.go
# and docs/contributing/react-agent.md (Agent identity).
dir: ""
# File names inside prompts.dir (defaults: agent.md, plan.md, ask.md)
# agent_prompt: "agent.md"
# plan_prompt: "plan.md"
# ask_prompt: "ask.md"
# A template that does not print the AGENTS.md and DESIGN.md documents still
# gets them: in {{.Instructions}} when it has that slot, otherwise appended
# right after it together with the files below.
# AGENTS.md and DESIGN.md are always read, in three layers, and are not named
# here: ${CODDY_HOME}/AGENTS.md and DESIGN.md, then the session folder's pair,
# then the pair of every folder on the way down to a file the agent works with.
# No setting turns them off. instructions.files only ADDS files after them, in
# the order listed; ${CODDY_HOME}, ${CWD} and ~ expand, a relative entry is read
# from the session folder, and a file already in the prompt is not read twice.
# instructions:
# files:
# - "${CODDY_HOME}/team.md"
# - "/srv/agents/house-style.md"
skills:
# Four folders are always read, lowest -> highest priority (a later one
# wins a skill name): ${HOME}/.agents/skills, the project's .agents/skills,
# ${CODDY_HOME}/skills, the project's .coddy/skills. dirs only ADDS
# directories, read after the four and stronger than them. ${CWD} and a
# relative entry are the session workspace, resolved when a session loads
# its skills, so they are safe here even for a shared coddy serve server.
dirs: []
# Remote skill sources and marketplaces are not declared here: they live in
# ${CODDY_HOME}/marketplaces.json (yours) and the project's
# .coddy/marketplaces.json, {"sources": [...], "marketplaces": [{"name", "source"}]},
# written by `coddy skills add [--project]`, `coddy plugin marketplace add`
# and Settings -> Skills. Declaring installs nothing: `coddy plugin install`
# installs a plugin or a source, `coddy plugin marketplace update` reads the
# lists and names the newer versions, `coddy plugin update` installs them.
# An old skills.sources list moves into the home file on the first load.
# The catalogue rpa-skills (EvilFreelancer/rpa-skills), which the bundled
# rpa-* skills are published from, is always in effect and always trusted
# beside both files; it installs nothing on its own, and a skill of it you
# delete stays deleted.
#
# project_trust decides the project's marketplaces.json, which travels with
# the checkout: "ask" (default) uses an entry only once it is approved for
# the workspace (`coddy plugin marketplace trust <name>` or the shield in
# Settings -> Skills), "allow" treats it like your own file, "deny" never
# uses it. Whatever any entry installs goes to ${CODDY_HOME}/skills.
project_trust: ask
# auto_discovery offers the model-driven load_skill tool so the agent can pull a
# catalogued skill's full instructions into a turn on its own when the request
# matches (like Codex / Claude Code / Cursor), instead of only when you type /name.
# Defaults to true; set false to keep skills manual-only. Also settable via the
# `--skills-auto-discovery=false` CLI flag on `coddy acp` / `coddy serve`.
auto_discovery: true
# MCP servers are not declared in this file. They live in two Cursor-compatible
# files, a "mcpServers" object keyed by server name like Cursor's
# ~/.cursor/mcp.json:
# ${CODDY_HOME}/mcp.json the global servers, started for every session;
# <workspace>/.coddy/mcp.json the project's, which win a name over the global
# ones and run only once approved (mcp.project_trust).
# A value may name an environment variable as ${NAME} (or ${NAME:-default}),
# resolved when the server starts, the same in both files, so a token stays
# out of the file:
# {"mcpServers": {"github": {"command": "npx",
# "args": ["-y", "@modelcontextprotocol/server-github"],
# "env": {"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"}}}}
# Add and edit them in Settings -> MCP servers or in the files themselves,
# switch them there or with /mcp in a chat or the console. An old mcp_servers
# list in this file is moved into ${CODDY_HOME}/mcp.json on the next start
# (config.yaml.bak-<time> keeps the old file).
# MCP settings that are not tied to a single server entry.
mcp:
# Trust policy for <workspace>/.coddy/mcp.json. That file travels with the
# checkout, so the repository - not you - picks the command a session would
# start on session/new.
# ask: keep those servers cold until you approve that exact declaration for
# that workspace (`coddy mcp list` / `coddy mcp trust <name>`, the same
# decisions in Settings -> MCP servers). Default.
# allow: start them automatically (only for workspaces you already trust)
# deny: never load project-local servers at all
project_trust: ask
# Every session shares one running copy of a server: a global server is one
# process for the whole Coddy process (coddy serve, the console and coddy acp
# start them at once and keep them up), a project server one per workspace.
# Seconds a server that no session holds any more keeps running before it is
# stopped, so the next session finds it up; 0 stops it at once. A server
# switched off, no longer approved or removed stops at once either way.
idle_timeout_seconds: 300
tools:
# The permission mode (ask, accept_edits, bypass) is not configured here. A new
# session starts in the mode chosen last on any surface (/permissions, the web
# composer chip, the permission dialog, --permission-mode), kept in
# ${CODDY_HOME}/permission-mode.json; ask until one is chosen. Each session keeps
# its own mode in its session.json. An old tools.permission_mode moves into that
# file on the next start.
# Commands that never require user permission.
# Supports exact match and prefix match (prefix + space + any args).
# Use "*" to allow all commands without asking (disables permission for commands entirely).
command_allowlist:
- "go build"
- "go test"
- "go vet"
- "go fmt"
- "go mod tidy"
- "make"
- "git status"
- "git diff"
- "git log"
# TCP dial timeout for SSH connections in seconds (default: 30).
# ssh_connect_timeout: 30
# Per-tool ceilings on how many lines a result or error may add to the LLM
# context. Enabled limits also impose a 64 KiB safety ceiling so a huge single
# line cannot bypass them. 0 = unlimited. Truncated output ends with a marker.
# output_limits:
# read: 1000 # file page / directory listing
# grep: 200 # path:line:content records
# glob: 300 # paths
# print_tree: 400 # directory tree lines
# run_command: 500 # stdout+stderr lines
# ssh_run_command: 500
# webfetch: 800 # page markdown lines
# websearch: 200 # search result lines
# default: 1000 # anything else, incl. MCP tools; 0 = unlimited
# Commands the agent runs detached in the session background task pool instead
# of blocking a turn. run_command gains "background" and "expected_seconds";
# background_list / background_output / background_wait / background_stop
# collect the result later. The pool dies with the coddy process: metadata and
# captured output stay in the session bundle, and a task interrupted by a
# restart is reported as orphaned. 0 = use the built-in default.
# background:
# enable: true
# max_concurrent: 5 # concurrent background tasks per session
# default_timeout_seconds: 900 # hard limit without a timeout or an estimate
# max_timeout_seconds: 3600 # ceiling for any requested or estimated timeout
# output_buffer_bytes: 262144 # in-memory output window; full log goes to disk
# The preview_server tool: a static file server over a project directory, on a
# free port, so you can open HTML/JS/CSS work in a browser ("run this on a web
# server, I want to try it myself"). It is a background task: it runs until it
# is stopped, the session is deleted or coddy exits. Dot-files are never served.
# preview_server:
# enable: true
# host: 127.0.0.1 # bind address; 0.0.0.0 inside a container exposes the files
# public_host: "" # host written into the URL, when the browser is elsewhere
# The canvas of the web UI: a .canvas file of the workspace shown in the Files window
# as a board of live frames, each a page of the workspace (HTML, CSS, JS) running in a
# sandbox. The agent puts pages there with canvas_show and reads what the browser saw
# (load, console errors) with canvas_inspect; you point at an element and say what to
# change. A page reads every file under its folder and can send it to the hosts it may
# load from.
# canvas:
# enable: true
# allow_cdn: true # jsDelivr, unpkg, cdnjs, the Tailwind CDN, Google Fonts
# allowed_sources: [] # further hosts, e.g. https://cdn.example.com
# Web search: which engines the websearch tool asks, in what order their results merge,
# and what it may spend asking them. Every engine reports its own outcome next to the
# results ("ok", "empty", or "blocked" with a reason), so a backend that was turned away
# is named instead of being counted as "the web has nothing".
#
# The default set is brave then bing. "ddg" and "google" are available but not asked by
# default: from a server DuckDuckGo answers every query with an anti-bot page and Google
# renders its results in the browser, so both return nothing. "searxng" points at your
# own instance and is the durable choice - it is not rate-limited against its owner and
# is never served a decoy result set. See docs/features/web-search.md.
# websearch:
# engines: [brave, bing] # merge order; brave, bing, ddg, google, searxng
# engine_timeout_seconds: 8 # per-engine budget
# total_timeout_seconds: 20 # budget for the whole search
# max_concurrent_engines: 4 # how many engines are asked at once
# snippet_chars: 320 # per-result description cap
# cache_ttl_seconds: 300 # reuse an engine answer for this long; negative = off
# searxng_url: "" # e.g. http://localhost:8080 (enable format: json there)
# brave_api_key: "" # official Brave Search API instead of the public page;
# # empty reads BRAVE_API_KEY (environment or ~/.coddy/.env)
# HTTP requests: the http_request tool is the agent's curl (any method, headers, bodies,
# multipart uploads, downloads to a file, a proxy and the certificate check per request).
# Under the permission modes ask and accept_edits a request asks unless its destination is
# listed here or was approved in the session, and an approval of a destination does not
# approve the files, the proxy or the unchecked certificate of a later request by itself.
# An entry here also covers uploads and verify_tls: false for that destination; a proxy
# needs an entry of its own, and under ask a download to a file still asks. The tool
# refuses no address (localhost included), so bypass reaches anything the host can.
# default_headers go out with every request unless the call names the header itself
# (a call's value wins, and an empty value leaves the header out); Host, Content-Type,
# Content-Length, Transfer-Encoding, the hop-by-hop headers (Connection, Upgrade, ...) and
# Proxy-Authorization are refused. They reach every destination the
# model sends a request to, so keep credentials out unless that is the intent.
# webfetch and the model providers never send them.
# See docs/features/http-requests.md.
# http_request:
# allowlist:
# - api.github.com # a host, any scheme and port
# - "*.internal.example.com" # subdomains only
# - localhost:8080 # host and port
# - https://api.example.com/v1/ # an address prefix
# default_headers:
# User-Agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
# Accept: application/json
# Subagents: child agents the model can delegate to with spawn_agent. A definition is a
# markdown file with YAML frontmatter (name, description, model, mode, tools,
# permission_mode, ...) whose body is the child's role; each run is a background task of
# the parent session with its own child session and transcript. Definitions found inside
# the workspace travel with the checkout, so "ask" refuses to spawn one until you approve
# that file for that workspace on the machine running coddy (`coddy agents trust <name>`
# there, or `POST /coddy/subagents/{name}/trust`). Four folders are always read, the
# later winning a name: ~/.agents/agents, the project's .agents/agents,
# ${CODDY_HOME}/agents and the project's .coddy/agents; dirs only adds folders after
# them. See docs/features/subagents.md.
# subagents:
# enable: true
# dirs:
# - "${CWD}/.claude/agents" # project scope, Claude Code compatible
# project_trust: ask # ask | allow | deny
# max_concurrent: 4 # subagent runs in flight across the whole process
# max_depth: 1 # 1 = children cannot spawn further (a definition's
# `spawns` allowlist excepted); 0 = nobody spawns
# default_timeout_seconds: 1800 # hard limit when the definition and the call give none
# max_turns: 0 # 0 follows agent.max_turns
# Hooks: your own commands run at lifecycle points of a session (before and after a tool
# call today; prompt, stop, session-start, compaction and subagent events follow). A hook reads
# one JSON document on stdin and answers with an exit code plus optional JSON on stdout: it can
# deny a call, approve it past the permission prompt, rewrite its arguments or add context.
# Definitions use Claude Code's file shape. Files inside the workspace travel with the checkout,
# so "ask" lists them but runs nothing until you approve that file for that workspace on the
# machine running coddy (`coddy hooks trust <file>` there, or `POST /coddy/hooks/trust`).
# See docs/features/hooks.md.
# hooks:
# enable: true
# files:
# - "${CODDY_HOME}/hooks.json" # your own file (user scope)
# - "${CWD}/.claude/settings.json" # project scope, Claude Code compatible (hooks key only)
# - "${CWD}/.claude/settings.local.json" # project scope, same
# - "${CWD}/.coddy/hooks.json" # project scope
# project_trust: ask # ask | allow | deny
# default_timeout_seconds: 60 # per hook process when the definition gives no timeout
# stop_loop_limit: 5 # how many times per turn a Stop hook may keep the agent working
# max_output_chars: 10000 # cap on the context, messages and reasons one hook hands over
# OpenAI-compatible HTTP API (only when built with: go build -tags=http). Embedded SPA from GET / adds -tags=ui with http. See docs/reference/http-api.md
# Run it with `coddy serve`, which starts every subsystem enabled below in one
# process over one set of sessions.
# httpserver:
# enable: true # omitted means true; set false for a node that only polls a bot or relays a swarm
# # Empty means 127.0.0.1. One process now starts every enabled subsystem, so
# # asking for a Telegram bot must not open the agent API to the network as a
# # side effect. Set 0.0.0.0 to accept connections from other machines.
# host: "127.0.0.1"
# port: 8080
# # Optional bearer auth. Empty = no auth (default). When set, /v1/* and /coddy/* require
# # "Authorization: Bearer <token>". Prefer --auth-token / CODDY_HTTP_TOKEN to keep the
# # secret out of this file; a "${ENV}" reference works too. Never returned by GET /coddy/config.
# auth_token: "${CODDY_HTTP_TOKEN}"
# # Web UI sign-in: the browser's half of the same gate, off unless an account exists.
# # Write it with `coddy serve set-password --user NAME` rather than by hand - the hash is
# # argon2id and every "$" of it has to be doubled to survive this file's ${ENV} expansion.
# # CODDY_HTTP_USER / CODDY_HTTP_PASSWORD (see ~/.coddy/.env) enable the form on their own,
# # win over an account here, and are never written into this file. An explicit
# # enable: false switches the form off with those variables still set.
# # A form is for browsers: coddy --remote, coddy acp --remote, a swarm relay and scripts
# # keep presenting auth_token, so set both when anything but a browser talks to this server.
# login:
# enable: true
# user: "pasha"
# password_hash: "$$argon2id$$v=19$$m=19456,t=2,p=1$$..."
# session_ttl_hours: 720 # 0 = the browser drops the cookie on close; the server still expires its record after 30 days
# public_docs: false # when auth is on, keep /docs and /openapi.* reachable without a token
# allow_insecure: false # silence the warnings about a server without a credential: a non-loopback
# # bind, and cors.allow_loopback or "*" (and their --dry-run findings)
# # CORS lets a browser UI on another origin call this API (needed when the UI points here as a
# # remote). Bearer auth still applies. allowed_origins accepts exact origins or a single "*";
# # allow_loopback admits every page served from the browser's own machine (localhost,
# # *.localhost, 127.0.0.0/8, [::1]) on any port - a laptop's own coddy serve, whatever its
# # port. Neither "*" nor allow_loopback is safe without a token or the sign-in form.
# cors:
# enable: false
# allow_loopback: false
# allowed_origins: ["http://localhost:12345"]
# # Remote coddy serve servers and swarm relays offered in the UI environment selector and
# # resolved by `coddy --remote <name>`. An entry may carry the token to present (usually a
# # ${ENV} reference): every browser that reads this config receives it, which is the choice
# # of whoever writes it here. Without one, the UI keeps the token in the browser per remote
# # and the console reads --remote-token or CODDY_REMOTE_TOKEN. A relay also has to admit the
# # page's origin in its swarm.cors: listed in allowed_origins, or allow_loopback for a page
# # served from the browser's own machine.
# remotes:
# - name: "prod box"
# url: "https://box.example:12345"
# - name: "office relay"
# url: "http://relay.lan:12346"
# token: "${CODDY_RELAY_TOKEN}" # the relay's client token (its swarm.auth_token)
# Embedded web UI (only with -tags http,ui). Set enable: false to run an API-only server
# (e.g. a remote box) — the SPA at GET / is replaced by a short notice, and the API keeps
# requiring httpserver.auth_token when configured. Defaults to enabled when omitted.
# ui:
# enable: true
# Cron scheduler (external/scheduler + lib/, tools/; build with: go build -tags=scheduler).
# Jobs live in two fixed folders: your own in ${CODDY_HOME}/scheduler, and the project jobs a repository carries in
# <workspace>/.coddy/scheduler, which run only once approved (project_trust). YAML frontmatter description, schedule
# (5-field crontab, UTC), optional cwd, model, mode; markdown body is the one-shot agent instruction. Sidecar: basename.state
# (kept in ${CODDY_HOME} for a project job).
# Enable with scheduler.enable, or per run with `coddy serve --scheduler`.
# scheduler:
# enable: false
# project_trust: ask # ask (approve each project job), allow (run them like your own) or deny
# max_queue: 10 # concurrent scheduled runs (default); when saturated the daemon skips extras until slots free (see logs)
# timeout: "30m" # per scheduled agent run
# Long-term memory subagent (external/memory; needs the memory build tag). Toggle with memory.enable.
# Every user turn starts a memory subagent in the background task pool: a child session that searches, reads and
# saves markdown under memory.dir (default ${CODDY_HOME}/memory) and <session cwd>/.coddy/memory (project). Its report reaches
# the main model in the <turn_context> block after the history, never in the system prompt, so the provider's prompt
# cache keeps covering the conversation; the main agent never sees the memory_* tools. The run is
# watched and kept in the Tasks panel like a spawn_agent child (docs/features/memory.md).
# memory.model is the model the memory subagent runs on. Set it to an exact models[].model id when you want memory
# pinned independently of agent.model (example: "rpa/qwen3.6-35b-a3b"). Leave empty to use the session's model.
# memory:
# enable: false
# model: ""
# fallback_models: [] # tried in order when the model above them fails before answering; the session
# # model is the last resort whether or not it is listed
# dir: "" # default ${CODDY_HOME}/memory when empty
# wait_seconds: 20 # how long a turn waits for the report before its first model call; 0 never waits
# timeout_seconds: 300 # hard limit of one memory run (capped by tools.background.max_timeout_seconds)
# keep_runs: 20 # finished memory runs kept per session in the Tasks panel; 0 keeps all
# recall_max_turns: 6 # the child's round cap is the larger of the two
# persist_max_turns: 12
# copilot_max_tokens: 4096
# max_search_hits: 8
# max_note_chars: 900 # longest body one saved note may have, in characters; 0 = no cap
# additional_prompt: "" # your own instructions for the memory subagent only, e.g. "only deal with the
# # notes; never answer the task itself"; the main agent never sees them
# additional_prompt_max_chars: 0 # cut additional_prompt at this many characters (a warning is logged); 0 = no cap
# Command safety decisions: before the permission gate decides on a shell command
# (run_command, or the remote command of ssh_run_command) you did not allow
# explicitly, ask the NeuralDeep decisions API whether it is safe. A safe command
# runs without a prompt; an unsafe one is asked about in every mode, bypass
# included, the verdict in the prompt, and rejected where nobody can answer - the
# rejection comes back as the result of the call, so the session records it. The
# command allowlist, a session grant of exactly that command, a hook's allow and
# commands you approved in a prompt are not checked.
# When the endpoint gives no verdict (no credential, a refusal from the hub, a
# network error, a command longer than the model reads - 512 tokens on
# frida-decisions) the command counts as unsafe. Needs a NeuralDeep credential on
# the first provider row of type neuraldeep: its api_key or api_key_command, its
# NAME_API_KEY variable (NEURALDEEP_API_KEY for a row named neuraldeep or without a
# row), or its stored hub sign-in. Every checked command draws on the decisions
# quota - 1 unit on frida-decisions, 4 on clef-flash - and adds ~20 ms
# (frida-decisions) or ~150 ms (clef-flash).
# See docs/features/decisions.md.
# decisions:
# enable: false
# model: frida-decisions # frida-decisions (512 tokens of command text, ~20 ms) or clef-flash (8192 tokens, ~150 ms)
# threshold: 0.5 # unsafe once p(unsafe) reaches this; 0 = default 0.5, lower is stricter, higher is looser
# Messenger gateways (external/gateway/; build with: go build -tags gateway, or
# -tags gateway.telegram / -tags gateway.pachca for one adapter).
# The bot is not a command of its own: set enable: true and `coddy serve` polls
# Telegram alongside whatever else is on. With httpserver.enable the chats are
# live sessions in the web UI - watchable while they run, continuable in either
# place. See docs/surfaces/gateway.md.
# gateways:
# telegram:
# enable: false
# token: "${TELEGRAM_BOT_TOKEN}" # optional: omit to read TELEGRAM_BOT_TOKEN from env/.env
# # proxy: none # inherit (default: HTTPS_PROXY), none (direct), or a URL
# # proxy: "socks5h://127.0.0.1:1080" # http, https, socks5, socks5h
# # proxy: "http://proxy.example.com:3128"
# rich_messages: true # Bot API 10.1 Rich Messages: native Markdown, streamed
# # "Thinking…" while tools run, collapsible tool list.
# # Falls back to legacy formatting if unsupported. Default false.
# admins: [98874093] # Telegram user IDs with elevated rights
# default_access: "all" # all | admins | group:<name>
# default_isolation: "individual" # individual | shared | admin
# user_groups:
# - name: "devs"
# user_ids: [111222333, 444555666]
# chats:
# # Per-chat overrides (optional); chat_id is negative for groups/supergroups.
# - chat_id: -1001234567890
# isolation: "individual"
# access: "all"
# - chat_id: -1009876543210
# isolation: "admin" # only respond to admin users
# access: "admins"
# mini_app:
# # The web UI as the bot's Mini App: the menu button opens it and /app
# # opens the chat's own conversation there. Telegram opens https only, so
# # publish the web UI behind a TLS proxy with sign-in on
# # (`coddy serve set-password`): the bot does not advertise an open one.
# url: "https://coddy.example.com/"
# menu_button: true # default; false leaves the menu button to @BotFather
# # Pachca (Пачка) integration bot: reads the bot's events history, so the
# # machine needs no public address. Turn on "Save events history" in the
# # bot's outgoing webhook settings. See docs/surfaces/pachca.md.
# pachca:
# enable: false
# token: "${PACHCA_BOT_TOKEN}" # optional: omit to read PACHCA_BOT_TOKEN from env/.env
# # proxy: none # inherit (default), none (direct), or a URL
# poll_interval_seconds: 2 # 1 to 60
# admins: [12345] # Pachca user IDs with elevated rights
# default_access: "all" # all | admins | group:<name>
# default_isolation: "individual" # individual | shared | admin
# chats:
# - chat_id: 334 # a conversation, channel or thread chat id
# isolation: "shared"
# access: "all"
logger:
level: "info"
# Raise or lower one subsystem without turning the whole process to debug.
# A component is the dotted name a record carries in its "component" field
# (gateway, gateway.telegram, gateway.pachca, session, agent, scheduler); a parent name
# covers what is nested under it, and the longest match wins. The same spec
# works on the command line: --log-level "info,gateway.telegram=debug".
# levels:
# - component: "gateway.telegram"
# level: "debug"
# outputs: ["stderr"] # default when omitted
# outputs: ["stdout", "file"] # example: tee to console and file
# file: "${CODDY_HOME}/coddy.log" # required when outputs includes "file"
format: "text"
rotation:
max_size_mb: 0
max_files: 0
# ----------------------------------------------------------------------------
# swarm - a relay that nodes register into, and that chains into other relays.
# Server side needs -tags swarm and swarm.enable; the join list below is
# honoured by every coddy serve process, which is what lets an ordinary agent be
# reached through a relay.
# Full reference: docs/operate/swarm.md
# ----------------------------------------------------------------------------
# swarm:
# enable: false # run the relay here; independent of the join list below
# name: "outer" # empty: the host name this relay runs on
# host: "0.0.0.0"
# port: 12346
# # A relay reaches every node with that node's own credential, so an open one
# # hands its whole fleet to anybody who can reach it. Binding off loopback
# # without this refuses to start.
# auth_token: "${CODDY_SWARM_TOKEN}"
# # What a node must present to join. Empty closes registration.
# pairing_tokens:
# - "${CODDY_SWARM_PAIRING_TOKEN}"
# cors:
# enable: true
# allow_loopback: true # a laptop's web UI on any loopback port
# allowed_origins: ["https://ui.example"]
# tls:
# cert_file: "/etc/coddy/relay.crt"
# key_file: "/etc/coddy/relay.key"
# # Nodes listed by hand, for one that cannot run the join loop.
# upstreams:
# - name: "nas02"
# url: "https://nas02:12345"
# kind: "agent" # agent (default) or relay
# token: "${NAS02_TOKEN}"
# # Relays this process registers into, whether or not it relays itself.
# # That symmetry is the whole of relay chaining.
# join:
# - url: "https://relay.example"
# name: "nas02" # empty: the host name, dots turned into dashes
# pairing_token: "${CODDY_SWARM_PAIRING_TOKEN}"
# # Where the relay can reach this process. Leave it out and this process
# # dials the relay instead, which is the only way in from a network that
# # accepts no inbound connections.
# advertise_url: "https://nas02:12345"
# token: "${NODE_OWN_TOKEN}"
# labels:
# role: "builder"
# dial:
# proxy: "socks5://127.0.0.1:1080"
# ca_file: "/etc/coddy/internal-ca.pem"
# ----------------------------------------------------------------------------
# browser - Coddy for Browser, the extension that lets chats work in your
# browser (Chrome, Yandex Browser, Firefox) through the browser tool. It connects to the HTTP server and shows
# the web UI in its side panel. Needs a binary built with -tags browser;
# others refuse enable.
# Guide: docs/features/browser.md
# ----------------------------------------------------------------------------
# browser:
# enable: false # off by default
# token: "${CODDY_BROWSER_KEY}" # the key entered in the extension, 32+ characters
# serve_remote: false # let a console, an editor or a swarm node use these browsers
# remotes: [] # servers whose browsers this one's chats use
# ----------------------------------------------------------------------------
# metrics - the Prometheus exporter: GET /metrics on a port of its own, run by
# coddy serve. Needs a binary built with -tags metrics; others refuse enable.
# Full reference: docs/reference/config.md
# ----------------------------------------------------------------------------
# metrics:
# enable: false # off by default
# host: "127.0.0.1" # empty: 127.0.0.1; set a token before binding wider
# port: 12347 # 0: 12347
# token: "${CODDY_METRICS_TOKEN}" # a scrape must send it as a bearer token