Repository navigation
Expand file tree
/
Copy pathconfig.example.json
More file actions
252 lines (234 loc) · 16.3 KB
/
Copy pathconfig.example.json
File metadata and controls
252 lines (234 loc) · 16.3 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
{
"$comment": "Copy to config.json. API keys are read from env vars, not stored here. A key beginning with $ is documentation, not configuration: it is ignored wherever it appears, at any depth, so notes like this one can sit next to the values they describe (including inside agents and prices, which decode into maps).",
"default_mode": "plan",
"show_thinking": true,
"verbose": false,
"log_level": "info",
"providers": [
{
"name": "deepseek",
"type": "openai",
"model": "deepseek-flash",
"base_url": "https://api.deepseek.com",
"failover": ["openrouter"],
"$comment": "failover lists the routes to try, in order, when a call fails transiently (408/429/5xx, a network error, a silent stream) before anything reached the caller. The primary spends its own retry budget first. A listed route follows its own list too, so chains compose. A fallback uses its own configured model, and the call is billed by the route that answered. An empty or absent list (the default) never moves a call. The chain above is an example: remove it if you do not want a failed call billed to another route."
},
{
"name": "openai",
"type": "openai",
"model": "gpt-4o",
"base_url": "https://api.openai.com/v1"
},
{
"name": "openrouter",
"type": "openai",
"model": "anthropic/claude-sonnet-4",
"base_url": "https://openrouter.ai/api/v1",
"cost_currency": "credits",
"prompt_cache": { "mode": "explicit" },
"$comment": "Anthropic models cache only content marked with cache_control. OpenAI, DeepSeek and Gemini cache a stable prefix automatically, so \"auto\" (a routing key) is enough there; a native ollama route accepts neither."
},
{
"name": "anthropic",
"type": "anthropic",
"model": "claude-sonnet-4-20250514",
"prompt_cache": { "mode": "explicit" },
"thinking_budget_tokens": 2048,
"$comment": "Native Anthropic Messages API (roadmap A6). base_url defaults to https://api.anthropic.com and the key comes from ANTHROPIC_API_KEY (or api_key_env). prompt_cache explicit marks the last system block; auto is refused because Anthropic has no routing-key equivalent. thinking_budget_tokens is opt-in, passed through unchanged, and its thinking block is replayed with the vendor's signature on the next tool step."
},
{
"name": "gemini",
"type": "gemini",
"model": "gemini-2.5-pro",
"thinking_budget_tokens": 2048,
"$comment": "Native Gemini generateContent API (roadmap A6). base_url defaults to https://generativelanguage.googleapis.com/v1beta and the key comes from GEMINI_API_KEY, then GOOGLE_API_KEY. prompt_cache auto is accepted because Gemini caches a stable prefix automatically; explicit is refused because the cachedContents resource is not implemented."
},
{
"name": "bedrock",
"type": "bedrock",
"model": "anthropic.claude-3-5-sonnet-20241022-v2:0",
"region": "us-east-1",
"prompt_cache": { "mode": "explicit" },
"$comment": "Native Bedrock Converse API (roadmap A6), signed with SigV4 from AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and the optional AWS_SESSION_TOKEN. region falls back to AWS_REGION, then AWS_DEFAULT_REGION; base_url defaults to the regional bedrock-runtime host. prompt_cache explicit adds a cachePoint; auto and a 1h ttl are refused."
},
{
"name": "azure",
"type": "azure",
"model": "my-deployment",
"base_url": "https://my-resource.openai.azure.com",
"api_version": "2024-10-21",
"$comment": "Native Azure OpenAI (roadmap A6): model is the deployment in the path, the key comes from AZURE_OPENAI_API_KEY and travels in the api-key header, and api_version defaults to the pinned date. The request body is the same OpenAI-compatible shape, so retry, usage and failover behave identically."
},
{
"name": "groq",
"type": "openai",
"model": "llama-3.3-70b-versatile",
"base_url": "https://api.groq.com/openai/v1"
},
{
"name": "ollama",
"type": "openai",
"model": "qwen2.5-coder:7b",
"base_url": "http://localhost:11434/v1",
"api_key_env": "OLLAMA_API_KEY"
}
],
"context_length": 1000000,
"compression_threshold": 500000,
"searxng_url": "https://search.yusiwen.cn",
"sandbox": {
"project_root": "<PROJECT_ROOT>",
"deny_commands": ["rm -rf /", "sudo", "reboot", "shutdown"]
},
"truncation": {
"max_lines": 2000,
"max_bytes": 204800,
"max_tokens": 4000,
"tail_lines": 20,
"output_dir": "/tmp/tinycode/truncated"
},
"pricing": {
"$comment": "Optional. What a route charges per million tokens, for the routes that do NOT report a charge themselves (OpenRouter does; see cost_currency above). Keys are '<route>/<model>' where <route> is the provider name above; '*' is allowed for the route, the model or both. A zero detail rate falls back to the lane it is carved out of: cached and written input to the input rate, reasoning to the output rate. currency names the unit your bill is in and is deliberately not defaulted. An entry's own rates apply at every instant its windows do not cover; a window names the weekdays it covers (mon..sun, or full names, any case) and a UTC 'from'/'to' as HH:MM, from inclusive and to exclusive, with 00:00 or 24:00 meaning midnight at the end of the day. A window must repeat every lane its entry declares, may not overlap another window on a shared day, and may not cover the whole week: each of those is refused at startup, naming the entry and the window, rather than priced by a guess. The rates below are DeepSeek's real pair: off-peak is the entry's own rate, and the two peak blocks are windows (35 of 168 hours, 20.8% of the week). Chinese public holidays are not encoded, so a weekday holiday is priced at peak, 2x too high on those hours; DeepSeek's cache-write lane is not declared, so it falls back to the input rate. A call with no declared rate and no reported charge is displayed as unknown, not as zero.",
"currency": "USD",
"prices": {
"deepseek/deepseek-flash": {
"input_per_million": 0.15,
"output_per_million": 0.60,
"cache_read_per_million": 0.003,
"windows": [
{
"weekdays": ["mon", "tue", "wed", "thu", "fri"],
"from": "01:00",
"to": "04:00",
"input_per_million": 0.30,
"output_per_million": 1.20,
"cache_read_per_million": 0.006
},
{
"weekdays": ["mon", "tue", "wed", "thu", "fri"],
"from": "06:00",
"to": "10:00",
"input_per_million": 0.30,
"output_per_million": 1.20,
"cache_read_per_million": 0.006
}
]
},
"ollama/*": {
"input_per_million": 0,
"output_per_million": 0
}
}
},
"notify": {
"$comment": "Optional. When a background task or a long run finishes, say so. channel: terminal (default; an escape iTerm2/WezTerm/kitty/Ghostty/rxvt render as a notification, and a terminal that does not know it ignores it), desktop (the platform's own notifier: osascript on macOS, notify-send on Linux), both, or off. min_duration_seconds defaults to 30 and gates everything, so a two-second task stays quiet; 0 announces every completion. There is no sound, and no Windows notifier.",
"channel": "terminal",
"min_duration_seconds": 30
},
"budget": {
"$comment": "Optional cumulative token budgets, as the provider reports them. Both default to 0 = unlimited. max_tokens_per_run bounds one prompt's ReAct loop; max_tokens_per_session bounds every run on this session together. A run stops before its next provider call once either is reached.",
"max_tokens_per_run": 0,
"max_tokens_per_session": 0
},
"goal": {
"$comment": "Optional continuation policy for /goal (roadmap D7). One round is a whole turn -- dozens of tool calls, minutes of work -- so max_goal_rounds bounds how long a goal may keep going, not how long one round may be. Shown with the shipped defaults; a positive value replaces the default and zero or absent means the default, so a typo cannot remove a guard. idle_grace_seconds is the wait before an automatic round, so someone already typing gets in first (a run refuses concurrent input). no_progress_rounds blocks a goal after that many consecutive rounds made no tool call. Budget the money separately: with no max_tokens_per_session, 256 rounds of a funded model is a large bill.",
"max_goal_rounds": 256,
"idle_grace_seconds": 2,
"no_progress_rounds": 3
},
"agents": {
"$comment": "Per-agent overrides. Supported keys: max_steps, system_prompt, allowed_tools (whitelist), denied_tools (added on top of the agent's built-in rules), permissions (an explicit ruleset that REPLACES both the built-in ruleset and the two tool lists: [{action, resource, effect: allow|deny}], last match wins), model. A model of the form \"<route>/<model>\" selects the provider as well: the route must be one of the names in providers[] above, the model half overrides that route's own model, and an unknown route stops startup. A value without a slash is a bare model override on the session's active provider. A sub-agent inherits its own pin, not the session's provider.",
"plan": {
"max_steps": 20,
"denied_tools": ["write_file", "git_commit", "sandbox_allow", "task", "skill_manage"]
},
"explore": {
"max_steps": 15
},
"general": {
"max_steps": 20,
"permissions": [
{"action": "*", "resource": "*", "effect": "allow"},
{"action": "task", "resource": "*", "effect": "deny"},
{"action": "task_collect", "resource": "*", "effect": "deny"},
{"action": "skill_manage", "resource": "*", "effect": "deny"},
{"action": "todo", "resource": "*", "effect": "deny"}
]
}
},
"session_dir": "~/.tinycode/sessions",
"checkpoint_dir": "~/.tinycode/checkpoints",
"trajectory": {
"$comment": "ON by default: a run that leaves nothing behind cannot be compared with another one. capture is \"metadata\" (an sha256 and a length for each tool argument and result) or \"full\" (the text as well, up to 64 KiB per payload). Metadata is the default because this writes to disk on every run and tool arguments carry secrets, so \"full\" is honoured only when it comes from your own ~/.tinycode/config.json — a project-local .tinycode/config.json that asks for it is ignored and the ignore is logged. max_runs and max_bytes bound the store (defaults 200 and 64 MiB); past either bound the oldest run files are removed first, and a run still being written is never removed. The directory is not a tool path: a tool that tries to write it is refused with 'ask: reserved', as with the checkpoint store, because a record the recorded agent can rewrite is not evidence. A killed run leaves every line up to the point of the kill and no run.end; `tinycode --trajectories` reports that run as incomplete rather than as broken. See docs/trajectories.md.",
"enabled": true,
"capture": "metadata",
"max_runs": 200,
"max_bytes": 67108864
},
"lsp": {
"enabled": false
},
"memory": {
"$comment": "Long-term memory. ON by default, local, and it stores nothing unless you ask: /remember is the only writer, and recall reads one file per project under dir (default ~/.tinycode/memory, deliberately outside the project root and refused to tools, because a fact the agent can rewrite is not one). backend is 'local' or 'hindsight'. A network backend sends each run's prompt to that service, so it is honoured only when it comes from your own ~/.tinycode/config.json — a project-local .tinycode/config.json that names one is ignored and the ignore is logged. 'hindsight' requires base_url (there is no default endpoint) and takes its bank from 'bank' (default 'default'). max_entries and max_bytes bound the store (oldest facts dropped first); recall_limit is how many facts one run may inject. The store is lexical today: embeddings are roadmap B3, so an absent keyword is an absent fact. See docs/memory-decision.md.",
"enabled": true,
"backend": "local",
"max_entries": 2000,
"max_bytes": 8388608,
"recall_limit": 5,
"embedding": {
"$comment": "OFF by default. Turns recall from keyword matching into vector similarity (roadmap B3). provider is 'ollama' (local, POST /api/embed) or 'openai' (any OpenAI-compatible /embeddings); base_url and model are required because there is no default endpoint. The endpoint receives each fact you remember and the prompt of every run, so — like a network backend — it is honoured only from your own ~/.tinycode/config.json. Vectors live in a sidecar next to the facts, keyed by model: changing model drops the old vectors and the next recall re-embeds the facts. min_score is the cosine floor, semantic_weight/lexical_weight blend the two rankings, backfill is how many facts one recall may embed.",
"enabled": false,
"provider": "ollama",
"base_url": "http://127.0.0.1:11434",
"model": "nomic-embed-text",
"min_score": 0.25,
"semantic_weight": 0.7,
"lexical_weight": 0.3,
"backfill": 16
}
},
"mcp_servers": [
{
"name": "example-stdio",
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem"]
},
{
"name": "example-http",
"transport": "http",
"url": "http://localhost:9000/mcp",
"headers": {
"Authorization": "Bearer mytoken"
}
}
],
"theme": "nord",
"$env_vars_note": {
"description": "API keys are loaded from environment variables. By default, the pattern is UPPER(NAME)_API_KEY. Use 'api_key_env' to specify a custom env var name (e.g., Ollama below).",
"examples": {
"DEEPSEEK_API_KEY": "set your DeepSeek API key",
"OPENAI_API_KEY": "set your OpenAI API key",
"OPENROUTER_API_KEY": "set your OpenRouter API key",
"GROQ_API_KEY": "set your Groq API key"
},
"override": "To use a custom env var name, add 'api_key_env' to the provider config. Example: { name: 'ollama', api_key_env: 'OLLAMA_API_KEY' }"
},
"$new_fields": {
"searxng_url": "Self-hosted SearXNG fallback for web_search. Remove or set to '' to disable.",
"theme": "TUI theme name: 'default' or 'nord'",
"context_length": "Maximum context window in tokens (default: 1000000)",
"compression_threshold": "Token count threshold to trigger history compression (default: 500000)",
"sandbox": {
"project_root": "Restrict file tool access to this directory (default: current dir)",
"deny_commands": "Shell commands that are always blocked"
},
"mcp_servers": "List of MCP servers. Supported transports: 'stdio' (local subprocess) and 'http' (remote endpoint).",
"session_dir": "Where sessions are stored. A leading ~ is expanded to $HOME.",
"checkpoint_dir": "Where the bytes a file tool replaced are kept, so /undo and --undo can restore them. Deliberately outside the project root: the fence that confines a tool write then confines the recovery store. It is not a tool path — no permission grants a model access to it. A bash command's writes are recorded too, from a snapshot of the project tree taken before it, bounded per file (2 MiB) and per snapshot (32 MiB / 4000 files); paths outside the project root are not captured. See docs/checkpoints.md.",
"trajectory": "Where a run's record goes (dir), how much of it keeps text (capture: metadata|full), and the store's bounds (max_runs, max_bytes). enabled: false turns recording off. capture: full is honoured only from your own config file, never from a project-local one.",
"memory": "Long-term memory: enabled, backend (local|hindsight), dir, base_url, bank, max_entries, max_bytes, recall_limit, embedding {enabled, provider, base_url, model, api_key_env, min_score, semantic_weight, lexical_weight, backfill}. Local by default and written only by /remember; a network backend, a base_url or an embedding endpoint is an egress request honoured only from your own config file.",
"truncation": "Tool-output truncation limits: max_lines, max_bytes, max_tokens (the bound that matters, since the preview is what the model pays for), tail_lines (how many of the last lines are kept beside the head, so a failure survives), output_dir (used only when the output exceeds a limit).",
"agents": "allowed_tools replaces the agent's ruleset with a whitelist; denied_tools is applied on top of the built-in rules."
}
}