@zenithfoundry/slm-gate 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/.env.example +669 -0
  2. package/LICENSE +21 -0
  3. package/README.md +317 -0
  4. package/configs/antigravity/.env.16gb.example +674 -0
  5. package/configs/antigravity/.env.24gb.example +674 -0
  6. package/configs/antigravity/.env.32gb.example +674 -0
  7. package/configs/antigravity/README.md +109 -0
  8. package/configs/claude-code/.env.16gb.example +674 -0
  9. package/configs/claude-code/.env.24gb.example +674 -0
  10. package/configs/claude-code/.env.32gb.example +674 -0
  11. package/configs/claude-code/README.md +52 -0
  12. package/configs/claude-desktop/.env.16gb.example +674 -0
  13. package/configs/claude-desktop/.env.24gb.example +674 -0
  14. package/configs/claude-desktop/.env.32gb.example +674 -0
  15. package/configs/claude-desktop/README.md +37 -0
  16. package/configs/cline-continue-opencode/.env.16gb.example +674 -0
  17. package/configs/cline-continue-opencode/.env.24gb.example +674 -0
  18. package/configs/cline-continue-opencode/.env.32gb.example +674 -0
  19. package/configs/cline-continue-opencode/README.md +34 -0
  20. package/configs/cursor/.env.16gb.example +674 -0
  21. package/configs/cursor/.env.24gb.example +674 -0
  22. package/configs/cursor/.env.32gb.example +674 -0
  23. package/configs/cursor/README.md +26 -0
  24. package/configs/generic-http/.env.16gb.example +674 -0
  25. package/configs/generic-http/.env.24gb.example +674 -0
  26. package/configs/generic-http/.env.32gb.example +674 -0
  27. package/configs/generic-http/README.md +20 -0
  28. package/configs/generic-stdio/.env.16gb.example +674 -0
  29. package/configs/generic-stdio/.env.24gb.example +674 -0
  30. package/configs/generic-stdio/.env.32gb.example +674 -0
  31. package/configs/generic-stdio/README.md +24 -0
  32. package/configs/preserve/README.md +26 -0
  33. package/configs/preserve/tls.json +61 -0
  34. package/dist/adapters/tech-lead-stack.js +38 -0
  35. package/dist/cache/index.js +173 -0
  36. package/dist/cli.js +256 -0
  37. package/dist/config.js +255 -0
  38. package/dist/dashboard/data.js +149 -0
  39. package/dist/dashboard/export.js +42 -0
  40. package/dist/dashboard/serve.js +63 -0
  41. package/dist/doctor.js +338 -0
  42. package/dist/hardware.js +126 -0
  43. package/dist/home-dir.js +39 -0
  44. package/dist/ledger/flush-lifecycle.js +50 -0
  45. package/dist/ledger/index.js +946 -0
  46. package/dist/ledger/report.js +69 -0
  47. package/dist/ledger/setup-dashboard.js +456 -0
  48. package/dist/ledger/smoke.js +37 -0
  49. package/dist/ledger/sync-config.js +177 -0
  50. package/dist/ledger/sync.js +307 -0
  51. package/dist/ledger/verify.js +185 -0
  52. package/dist/ledger/wipe-langfuse.js +130 -0
  53. package/dist/llm-gate/distill.js +239 -0
  54. package/dist/llm-gate/formats/anthropic.js +185 -0
  55. package/dist/llm-gate/formats/chat-completions.js +103 -0
  56. package/dist/llm-gate/formats/contract.js +29 -0
  57. package/dist/llm-gate/formats/gemini.js +84 -0
  58. package/dist/llm-gate/formats/internal.js +1 -0
  59. package/dist/llm-gate/formats/openai.js +77 -0
  60. package/dist/llm-gate/formats/responses.js +146 -0
  61. package/dist/llm-gate/forward.js +150 -0
  62. package/dist/llm-gate/index.js +40 -0
  63. package/dist/llm-gate/local-first.js +217 -0
  64. package/dist/llm-gate/pipeline.js +267 -0
  65. package/dist/llm-gate/server.js +289 -0
  66. package/dist/mcp-gate/ground.js +64 -0
  67. package/dist/mcp-gate/index.js +57 -0
  68. package/dist/mcp-gate/pipeline.js +252 -0
  69. package/dist/mcp-gate/server.js +302 -0
  70. package/dist/mcp-gate/tool-names.js +57 -0
  71. package/dist/models/check.js +26 -0
  72. package/dist/models/footprint.js +137 -0
  73. package/dist/models/helpers.js +91 -0
  74. package/dist/models/index.js +5 -0
  75. package/dist/models/reasoning.js +91 -0
  76. package/dist/models/roles.js +9 -0
  77. package/dist/models/slm.js +243 -0
  78. package/dist/models/types.js +1 -0
  79. package/dist/pricing/index.js +115 -0
  80. package/dist/pricing/plans.js +54 -0
  81. package/dist/pricing/providers.js +172 -0
  82. package/dist/resolver/index.js +277 -0
  83. package/dist/resolver/types.js +1 -0
  84. package/dist/setup/claim.js +41 -0
  85. package/dist/setup/gate-command.js +41 -0
  86. package/dist/setup/init.js +92 -0
  87. package/dist/setup/local-models.js +123 -0
  88. package/dist/setup/model-gate.js +220 -0
  89. package/dist/setup/notify.js +45 -0
  90. package/dist/setup/ollama-install.js +53 -0
  91. package/dist/setup/parent-watch.js +84 -0
  92. package/dist/setup/required-models.js +20 -0
  93. package/dist/setup/startup.js +132 -0
  94. package/dist/setup/tool-settings.js +101 -0
  95. package/dist/utils/backoff.js +47 -0
  96. package/dist/utils/compression.js +145 -0
  97. package/dist/utils/constants.js +22 -0
  98. package/dist/utils/duration.js +43 -0
  99. package/dist/utils/elision.js +556 -0
  100. package/dist/utils/embedding.js +32 -0
  101. package/dist/utils/entry-point.js +23 -0
  102. package/dist/utils/local-only.js +82 -0
  103. package/dist/utils/preserve-patterns.js +115 -0
  104. package/dist/utils/safety.js +30 -0
  105. package/dist/verifier/index.js +67 -0
  106. package/package.json +121 -0
@@ -0,0 +1,674 @@
1
+ # ==============================================================================
2
+ # SMALL-LANGUAGE-MODEL-GATE CONFIGURATION FOR: CLAUDE DESKTOP
3
+ # RAM PRESET: 24GB
4
+ # (Includes TLS configuration example)
5
+
6
+ # ==============================================================================
7
+ #
8
+ # HOW TO USE THIS FILE:
9
+ # 1. Copy this entire template to a new file named `.env`.
10
+ # 2. Open `.env` and fill in the values following the steps below.
11
+ # 3. This file is the SINGLE source of truth for env var NAMES. The agent must
12
+ # not invent new names; if a new variable is needed, extend this template first.
13
+ # 4. SECRETS: Leave API keys blank in this template file. Only put secrets in `.env`.
14
+ #
15
+ # --- CONFIGURATION STEPS (Top to Bottom Sequence) ---
16
+ #
17
+ # STEP 1: Local Small Model (SLM) Setup
18
+ # - SLM_PROVIDER: Choose your backend ('ollama' or 'openai' for OpenAI-compatible APIs).
19
+ # - OLLAMA_HOST: URL for your local inference server (e.g., http://localhost:11434).
20
+ # Include the http:// scheme; Ollama's own OLLAMA_HOST is a bare host:port and will not work here.
21
+ # - SLM_BRAIN_MODEL: Your "smart" local model used for drafting and heavy lifting.
22
+ # (Example: qwen3:14b, llama3:8b)
23
+ # - SLM_GATE_MODEL: Your fastest, smallest local model for routing and quick classification.
24
+ # (Example: qwen3:1.7b, phi3:mini)
25
+ # Not sure which models fit your computer? llmfit shows what runs best on it: https://github.com/AlexsJones/llmfit
26
+ # - SLM_GATE_TESTING_MODEL: Dedicated model for offline benchmarks and test suites (`slm-gate bench`).
27
+ # Defaults to matching SLM_GATE_MODEL to avoid VRAM exhaustion/timeouts during concurrent test runs.
28
+ # - NUM_CTX: Caps the context window. Large contexts rapidly inflate RAM usage via the KV cache.
29
+ # > What `slm-gate init` and `slm-gate doctor` pick for your RAM (src/hardware.ts recommendNumCtx), leaving
30
+ # > room for your OS and apps while both local models are loaded. slm-gate needs 16 GB of RAM or more:
31
+ # * 16GB RAM -> NUM_CTX=4096
32
+ # * 24GB RAM and more -> NUM_CTX=8192 (raise it if you like: more context uses more RAM, replies slower)
33
+ # - TEMPERATURE: Keep at 0 for deterministic output during logical routing and compression.
34
+ #
35
+ # STEP 2: Verifier Settings
36
+ # - STRICTNESS_LEVELS: Defines the available grading scales (0 = loose, 5 = max strictness).
37
+ # - HEADLINE_STRICTNESS: Set to a single integer to dictate the active strictness level (e.g., 4).
38
+ #
39
+ # STEP 3: API model (optional; NOT used by the model gate, which forwards each tool's own login)
40
+ # - Used only by the benchmark, the resolver's cloud tier and SLM_PROVIDER=openai.
41
+ # - CLOUD_API_STYLE: Set to 'openai' or 'anthropic'.
42
+ # - Fill in your API Key, Base URL, and specific Cloud Model.
43
+ # (Example: CLOUD_BASE_URL=https://api.openai.com/v1 | CLOUD_MODEL=gpt-5.6-terra)
44
+ # Semantic Cache (Optional)
45
+ # - SEMCACHE: Set to 'on' to enable local semantic caching of cloud requests.
46
+ # - SEMCACHE_THRESHOLD: Similarity threshold (0.0 to 1.0) for a cache hit.
47
+ # - EMBED_MODEL: The embedding model to use for the semantic cache.
48
+ #
49
+ # STEP 4: Server Configurations (Layer 1 & 2)
50
+ # - Set the ports for the LLM Gate (Layer 2) and MCP Gate (Layer 1).
51
+ # - The model gate's switches: LLM_GATE_AUTOSTART, LLM_GATE_DISTILL, LLM_GATE_LOCAL_FIRST,
52
+ # LOCAL_ATTEMPT_BUDGET_MS, and UPSTREAM_* (only behind a company gateway).
53
+ # - DOWNSTREAM_MCP: How the agent talks to tools. Leave blank to run standalone.
54
+ # If connecting to an MCP server, provide a JSON config:
55
+ # > Stdio Example: {"command":"node","args":["/[...]/[...]/[...]/ai-dev/agent-toolbox/tech-lead-stack/dist/mcp-server.mjs"],"env":{}}
56
+ # > HTTP Example: {"url":"https://host/mcp","headers":{"Authorization":"Bearer TOKEN"}}
57
+ #
58
+ # STEP 5: Logging & Telemetry (Ledger / Langfuse)
59
+ # - LEDGER_PATH: Where local SQLite logs are saved.
60
+ # - Langfuse: Optional cloud observability. Fill keys to enable, or leave blank to keep it local.
61
+ #
62
+ # STEP 6: Clarification Resolver & Misc
63
+ # - RESOLVER_CLOUD_TIER: Set to 'on' to permit bounded cloud API calls for unresolved queries.
64
+ # - RAM_PRESET: Your RAM size (ram-16, ram-24, ram-32, ram-48, ram-64, ram-128, or custom) picks default models.
65
+ # - Tool-Output Distillation & Elision: Configure caps for tool outputs and cache limits.
66
+ # (DISTILL_MAX_TOKENS, ELISION_MAX_MB, etc.)
67
+ # - Dynamic Routing Tuning: Toggle automatic exploration of routing thresholds.
68
+ #
69
+ # ==============================================================================
70
+ # ==============================================================================
71
+ # SMALL-LANGUAGE-MODEL-GATE ENVIRONMENT CONTRACT & SETUP GUIDE
72
+ # ==============================================================================
73
+ #
74
+ # HOW TO USE THIS FILE
75
+ # 1. Copy this whole file to `.env` -> cp .env.example .env
76
+ # 2. Fill in the values below (secrets ONLY go in `.env`, never in this template).
77
+ # 3. This file is the SINGLE source of truth for env var NAMES. The build agent
78
+ # must not invent new names; if a new setting is needed, add it here first.
79
+ #
80
+ # HOW TO READ EACH ENTRY
81
+ # Every variable has a comment block:
82
+ # What: what the value is
83
+ # Why: why it exists / what problem it solves
84
+ # Does: how the code actually uses it
85
+ # Values: allowed values / examples / defaults
86
+ #
87
+ # MENTAL MODEL (so the sections make sense)
88
+ # A request has two possible "gates":
89
+ # - LAYER 1 mcp-gate sits between your IDE and an MCP server; it *conditions*
90
+ # skill/prompt text with a LOCAL model before it reaches the cloud.
91
+ # - LAYER 2 llm-gate the "model gate": an HTTP server your coding tool sends its model
92
+ # requests to. It answers a first message LOCALLY when it can, shrinks
93
+ # large tool output, and sends the rest on to the tool's own provider
94
+ # with the tool's own login.
95
+ # "Local" model = the SLM you run in Ollama (free per token, costs only RAM + latency).
96
+ # "Cloud" model = the provider your coding tool already uses (Anthropic, OpenAI, Google).
97
+ # Everything is logged to a local SQLite "ledger" and (optionally) to Langfuse.
98
+ #
99
+ # ============================================================
100
+ #
101
+ # WHAT THIS TOOL DOES
102
+ # Runs a small AI model on YOUR computer to handle the easy parts of a
103
+ # request, so you hit the expensive PAID model less. Two parts, use either:
104
+ # - llm-gate : a local mini-server your coding tool sends its model requests to. Answers
105
+ # easy first messages with the local model, shrinks large tool output, and
106
+ # sends everything else to the provider the tool uses anyway, with the tool's
107
+ # own login (subscription or API key). Starts by itself (LLM_GATE_AUTOSTART).
108
+ # - mcp-gate : sits in front of a tool server (MCP) and trims the instructions
109
+ # before they reach whatever model your editor uses (often your subscription).
110
+ #
111
+ # WHO PAYS FOR WHAT
112
+ # Requests the gate sends on are billed exactly as without it: to your subscription or
113
+ # your API key, whichever the tool is logged in with. There are just fewer of them, and
114
+ # they are smaller. The "API model" in STEP 3 is a separate, optional key used only by
115
+ # the benchmark and the resolver's cloud tier.
116
+ #
117
+ # OTHER TERMS
118
+ # local model / SLM the small model you run via Ollama. Free to run.
119
+ # ledger a local log file of every request's cost + tokens (STEP 5).
120
+ # tech-lead-stack a SEPARATE, OPTIONAL project this can plug into. Not using it? Leave TLS off.
121
+ #
122
+ # MINIMUM TO GET RUNNING: just STEP 1. Everything else is optional.
123
+ # ============================================================
124
+
125
+
126
+ # ============================================================
127
+ # STEP 1 the local model (required)
128
+ # ============================================================
129
+
130
+ # Where the local model runs.
131
+ # ollama = on your machine (normal). openai = some other OpenAI-style server.
132
+ SLM_PROVIDER=ollama
133
+
134
+ # Address of your local Ollama. Leave as-is unless you changed Ollama's port.
135
+ # Must include http:// -- Ollama's own OLLAMA_HOST variable is a bare host:port, this one is a URL.
136
+ OLLAMA_HOST=http://localhost:11434
137
+
138
+ # The main local model (does the real work). Any Ollama tag works.
139
+ # Note: there is no `qwen3:9b`. For a 9B use `qwen3.5:9b` instead.
140
+ SLM_BRAIN_MODEL=qwen3.5:4b
141
+
142
+ # A tiny, fast local model for quick "is this easy?" checks and prompt distillation.
143
+ SLM_GATE_MODEL=qwen2.5-coder:3b
144
+
145
+ # Model used specifically for offline benchmarks and testing suites (`slm-gate bench`).
146
+ # Defaults to matching SLM_GATE_MODEL so high-concurrency benchmark batches avoid VRAM contention and timeouts.
147
+ SLM_GATE_TESTING_MODEL=qwen2.5-coder:3b
148
+
149
+ # How long models stay loaded in Ollama's memory (e.g. 5m, 1h, 12h, or -1 for indefinite).
150
+ OLLAMA_KEEP_ALIVE=12h
151
+
152
+ # ── DUAL-MODEL MEMORY CONCURRENCY (OLLAMA_MAX_LOADED_MODELS) ────────────
153
+ # By default, Ollama keeps only 1 model loaded in memory at a time.
154
+ # When running both a Gate model (e.g. 3B) and Brain model (e.g. 9B), Ollama will
155
+ # swap model weights back and forth between disk and GPU unless configured to hold both.
156
+ # If your machine has >=16GB - 24GB RAM (e.g. Apple Silicon M4 24GB):
157
+ # - Set OLLAMA_MAX_LOADED_MODELS=2 in your environment or shell profile:
158
+ # * macOS (persistent): `launchctl setenv OLLAMA_MAX_LOADED_MODELS 2`
159
+ # * Terminal / Linux: `export OLLAMA_MAX_LOADED_MODELS=2`
160
+ # > Note: Loading two models requires allocating two KV caches. On Apple Silicon,
161
+ # GPU memory is capped. If models get evicted, halve your NUM_CTX (24GB->8192, 16GB->4096)
162
+ # to ensure both models fit within your machine's GPU memory envelope.
163
+
164
+ # ── ⚠️ RAM TROUBLESHOOTING DISCLAIMER: IF YOUR RAM CONFIG IS NOT WORKING ──
165
+ # If models get evicted, Ollama is thrashing, or you hit OOM, consider:
166
+ #
167
+ # Memory Formula:
168
+ # Memory = Model Weights + (NUM_CTX × KV-Cache) × Models Loaded
169
+ #
170
+ # Dropping the brain model to a 7B is exactly the right lever, and yes it'll cut RAM.
171
+ # But don't just hand-edit NUM_CTX to a smaller number and call it done — memory is
172
+ # model weights + (NUM_CTX × KV-cache) × models loaded.
173
+ #
174
+ # General rule for all RAM capacities (e.g. moving down from 9B/14B):
175
+ # 1. Inspect pulled tags: `ollama list`
176
+ # 2. Pick a smaller brain: e.g. `qwen2.5:7b` (ollama pull qwen2.5:7b).
177
+ # Keep the small gate model (qwen2.5-coder:3b) as-is; it's already tiny (~2GB).
178
+ # 3. Set it in slm-gate's .env (the model gate reads only that file). If your coding
179
+ # tool's MCP config also sets these in an "env" block, change them there too: that
180
+ # block overrides .env for that tool's MCP server (e.g. Antigravity keeps it in
181
+ # ~/.gemini/config/mcp_config.json). When your tool starts slm-gate, it checks that
182
+ # the models named in both places are downloaded.
183
+ # Example "env" block:
184
+ # "SLM_BRAIN_MODEL": "qwen2.5:7b",
185
+ # "SLM_GATE_MODEL": "qwen2.5-coder:3b",
186
+ # "OLLAMA_MAX_LOADED_MODELS": "2",
187
+ # "NUM_CTX": "4096"
188
+ # 4. Lower NUM_CTX from 8192 -> 4096: This shrinks the KV-cache directly and is the
189
+ # single biggest knob after model size.
190
+ # 5. If still heavy: Set OLLAMA_MAX_LOADED_MODELS="1" to force one model in memory
191
+ # at a time (slower switching between models, but drastically lower RAM).
192
+ # 6. Confirm exact variable names: SLM_BRAIN_MODEL, SLM_GATE_MODEL, NUM_CTX, OLLAMA_MAX_LOADED_MODELS.
193
+ # 7. Sanity-check with doctor: run `pnpm run dev doctor` or `slm-gate doctor`.
194
+ # ───────────────────────────────────────────────────────────────────────────
195
+
196
+ # How much text the local model can hold at once. Bigger = more RAM used.
197
+ # 16GB->4096 24GB and more->8192 (what slm-gate init picks; src/hardware.ts recommendNumCtx)
198
+ NUM_CTX=8192
199
+
200
+ # Randomness of the local model. Keep 0 so the same input gives the same result.
201
+ TEMPERATURE=0
202
+
203
+ # Maximum time (in ms) to wait for a local model response per call.
204
+ # If an SLM call exceeds this limit, it safely fails-open (e.g. returning uncompressed text).
205
+ # Recommended values:
206
+ # * Fast GPU or small 1.5B-3B models: 30000 (30s)
207
+ # * 7B-14B models or CPU/unified RAM inference: 120000 (120s)
208
+ SLM_TIMEOUT_MS=120000
209
+
210
+ # The verifier re-asks the local model this many times to check it's consistent.
211
+ SELF_CONSISTENCY_K=3
212
+
213
+ # Randomness for those re-asks (must be above 0, or every re-ask is identical).
214
+ SELF_CONSISTENCY_TEMP=0.7
215
+
216
+
217
+ # ============================================================
218
+ # STEP 2 when to trust the local answer vs. use the API model
219
+ # ============================================================
220
+
221
+ # The "pickiness" ladder (the eval tool sweeps all of these). Keep as-is.
222
+ # 0 = always trust local 3 = + escalate if it hedges ("not sure")
223
+ # 1 = + escalate if empty 4 = + escalate if the re-asks disagree
224
+ # 2 = + escalate if wrong format 5 = always escalate to the API model
225
+ STRICTNESS_LEVELS=0,1,2,3,4,5
226
+
227
+ # Which level to actually use when running. 4 is a good default.
228
+ HEADLINE_STRICTNESS=4
229
+
230
+
231
+ # ============================================================
232
+ # STEP 3 the API model (metered, pay-per-token) — OPTIONAL, most people leave it blank
233
+ # The model gate does NOT use these: it sends each request on with the coding tool's own
234
+ # login (your subscription or your API key). These are read only by the benchmark
235
+ # (`slm-gate bench`), the resolver's optional cloud tier (STEP 6) and SLM_PROVIDER=openai.
236
+ # (These vars keep the CLOUD_ prefix to match the build playbook they mean the API model.)
237
+ # ============================================================
238
+
239
+ # Request format of the API provider. Gemini uses `openai`.
240
+ CLOUD_API_STYLE=openai
241
+
242
+ # The provider's API URL. Examples:
243
+ # OpenAI https://api.openai.com/v1
244
+ # Anthropic https://api.anthropic.com
245
+ # Gemini https://generativelanguage.googleapis.com/v1beta/openai/
246
+ CLOUD_BASE_URL=
247
+
248
+ # Your paid API key (this is the metered bill). Put the real value in `.env` only.
249
+ CLOUD_API_KEY=
250
+
251
+ # Exact model id from the provider.
252
+ # Gemini 3.1 Pro is `gemini-3.1-pro-preview` (NOT `gemini-3.1-pro`).
253
+ # List valid ids: curl -s "https://generativelanguage.googleapis.com/v1beta/models?key=$CLOUD_API_KEY" | jq -r '.models[].name'
254
+ CLOUD_MODEL=
255
+
256
+ # ── SEMANTIC CACHE ────────────────────────────────────────────────────────
257
+ # Caches the ANSWERS to read-only requests locally and reuses them when a new
258
+ # request is semantically near-identical, so a repeat question costs zero cloud
259
+ # tokens instead of a full round-trip. Unlike prompt caching (which only
260
+ # discounts repeated INPUT), this can skip the cloud call entirely.
261
+ #
262
+ # Safety scope: only read-only / explanatory turns are ever cached. Write
263
+ # actions and anything with side effects are never cached. Each entry is tied to
264
+ # the hash of the files it referenced, so if one of those files changes the entry
265
+ # is invalidated automatically (you won't get a stale answer for edited code).
266
+ #
267
+ # on | off. Default off — it changes behaviour (answers can come from cache), so
268
+ # it's opt-in. Turn it on once you're comfortable it's returning correct results.
269
+ SEMCACHE=off
270
+
271
+ # How similar a new request must be to a stored one to count as a "hit", measured
272
+ # as cosine similarity between their embeddings (0.0 = unrelated, 1.0 = identical).
273
+ # Trade-off:
274
+ # Higher (e.g. 0.97-0.99) = stricter: fewer hits, but only near-identical
275
+ # requests match, so almost no risk of answering the wrong question.
276
+ # Lower (e.g. 0.90-0.93) = looser: more hits and more savings, but a subtly
277
+ # different question can match and get the wrong cached answer.
278
+ # Start at 0.95 and only lower it if you're seeing few hits AND you've confirmed
279
+ # the hits you do get are correct. Valid range 0.0-1.0.
280
+ SEMCACHE_THRESHOLD=0.95
281
+
282
+ # The local Ollama embedding model used to measure that similarity. It runs
283
+ # on-device and is free (no cloud cost). You must pull it first:
284
+ # ollama pull nomic-embed-text
285
+ # Note: this defines the "meaning space" for the cache. If you change this model,
286
+ # existing cache entries are no longer comparable and should be cleared, or they
287
+ # simply won't match new requests. Keep it consistent once chosen.
288
+ EMBED_MODEL=nomic-embed-text
289
+
290
+
291
+ # ============================================================
292
+ # STEP 4 ports & connections (defaults are fine)
293
+ # ============================================================
294
+
295
+ # Port the model gate (llm-gate) listens on. Your coding tools send their model requests to
296
+ # http://localhost:8787; `slm-gate doctor` prints the exact line for each tool.
297
+ # Port taken by another program? Pick a free one here, run `slm-gate restart`, then run
298
+ # `slm-gate doctor` and paste the new lines into each coding tool.
299
+ # Set it only in this file: a value in a coding tool's MCP "env" block or in your shell is
300
+ # ignored when finding and starting the model gate, so every tool looks in the same place.
301
+ LLM_GATE_PORT=8787
302
+
303
+ # Start the model gate by itself. When a coding tool starts slm-gate's MCP server, the MCP server
304
+ # also starts the model gate in the background if it is not running, and brings it back within a
305
+ # minute if it stops. It keeps running until you log out or restart, or run `slm-gate stop`.
306
+ # on (default) | off = you start it yourself with `slm-gate start`.
307
+ # The model gate reads its settings ONLY from this .env file, never from a coding tool's MCP "env"
308
+ # block: one gate serves every tool, so it cannot take settings from whichever tool started it.
309
+ LLM_GATE_AUTOSTART=on
310
+
311
+ # Shrink large command, search and listing results (tool output) before a request leaves your
312
+ # machine. Each result is shrunk once and the same shorter text is resent every later turn, so
313
+ # the provider's prompt cache keeps working. on (default) | off = send requests unchanged.
314
+ LLM_GATE_DISTILL=on
315
+
316
+ # Let the local model try to answer the FIRST message of a conversation (not slash commands, not
317
+ # coding tasks that need the tool's tools). If it cannot, or its answer fails the check, the
318
+ # request goes on to your provider as normal. on (default) | off.
319
+ LLM_GATE_LOCAL_FIRST=on
320
+
321
+ # How long (ms) that first message may wait for a local answer before it goes on to your provider.
322
+ LOCAL_ATTEMPT_BUDGET_MS=6000
323
+
324
+ # Where the model gate sends each kind of request (optional).
325
+ # You don't need to fill any of these, whether you use a subscription or an API key. Leave all
326
+ # four blank. The gate already knows where each request goes and uses the correct address
327
+ # automatically.
328
+ # The only reason to set one: your company makes all AI traffic go through its own proxy server.
329
+ # Then you would put that proxy's address in the matching line. Everyone else leaves them blank.
330
+ # These four are split by provider, not by subscription vs API. Here is which one each login uses:
331
+ # UPSTREAM_ANTHROPIC_URL Claude Pro/Max login in Claude Code, AND an Anthropic API key
332
+ # default https://api.anthropic.com
333
+ # UPSTREAM_OPENAI_URL OpenAI API key (Codex, Cline and others)
334
+ # default https://api.openai.com/v1
335
+ # UPSTREAM_CHATGPT_URL ChatGPT login in Codex (subscription)
336
+ # default https://chatgpt.com/backend-api/codex
337
+ # UPSTREAM_GEMINI_URL Gemini API key only (a Google-account login can't go through the gate)
338
+ # default https://generativelanguage.googleapis.com
339
+ # Your coding tool's own login (subscription or API key) is sent either way.
340
+ UPSTREAM_ANTHROPIC_URL=
341
+ UPSTREAM_OPENAI_URL=
342
+ UPSTREAM_CHATGPT_URL=
343
+ UPSTREAM_GEMINI_URL=
344
+
345
+ # Which tool server (MCP) mcp-gate sits in front of. General setting — works with ANY MCP server.
346
+ # Not using a tool server -> leave BLANK (mcp-gate runs standalone: condition_prompt only).
347
+ # Using tech-lead-stack -> THIS MUST BE SET (TLS_ADAPTER=on requires it — see STEP 6).
348
+ # 1) build TLS once: cd /ABS/PATH/ai.tech-lead-stack && pnpm install && pnpm run mcp:build
349
+ # 2) set this to the built file (ONE line, valid JSON):
350
+ # DOWNSTREAM_MCP={"command":"node","args":["/[...]/[...]/[...]/ai-dev/agent-toolbox/tech-lead-stack/dist/mcp-server.mjs"],"env":{}}
351
+ # A different MCP server (HTTP) -> {"url":"https://host/mcp","headers":{"Authorization":"Bearer ${TOKEN}"}}
352
+ # Any file path inside the JSON must be the FULL path on your machine (see LEDGER_PATH below for why).
353
+ # Ships blank = standalone. Copy the example above onto the line below only if you are proxying a server.
354
+ DOWNSTREAM_MCP=
355
+
356
+ # How your editor connects to mcp-gate. stdio = normal local setup.
357
+ MCP_GATE_TRANSPORT=stdio
358
+
359
+ # Only used when MCP_GATE_TRANSPORT=http.
360
+ MCP_GATE_PORT=8788
361
+
362
+
363
+ # ============================================================
364
+ # STEP 5 logs: how you SEE what you saved
365
+ # Every request is logged locally to a small database (the "ledger"). It records
366
+ # LOCAL-model calls ($0) and API-model calls (real $). Your SUBSCRIPTION usage is NOT
367
+ # dollar-tracked here (flat fee) only its token/turn count.
368
+ # View the ledger either way:
369
+ # - locally: sqlite3 ./output/ledger.sqlite "select route,cost_usd,in_tok,out_tok from events;"
370
+ # (or run `slm-gate bench` for a summary + chart)
371
+ # - online: fill the 3 LANGFUSE_ keys below, then open your project at
372
+ # https://cloud.langfuse.com (same data, with dashboards)
373
+ # No Langfuse keys = local-only. That's fine.
374
+ # ============================================================
375
+
376
+ # ── LEDGER_PATH: MUST BE A FULL PATH ON YOUR MACHINE (or blank) ──
377
+ # Where the local log file lives.
378
+ # Blank (as shipped) -> output/ledger.sqlite inside the folder you installed slm-gate in,
379
+ # already resolved to a full path. This is the safe choice.
380
+ # If you set it -> use the FULL path from the root of your disk, specific to YOUR machine:
381
+ # macOS/Linux: LEDGER_PATH=/Users/yourname/projects/small-language-model-gate/output/ledger.sqlite
382
+ # Windows: LEDGER_PATH=C:\Users\yourname\projects\small-language-model-gate\output\ledger.sqlite
383
+ # (run `pwd` inside the repo folder and append /output/ledger.sqlite)
384
+ # NEVER a path relative to the repo such as ./output/ledger.sqlite.
385
+ # Why: your editor / MCP host launches slm-gate from a working directory that is NOT this repo
386
+ # (Claude Desktop launches from one that does not exist at all). A relative path is resolved
387
+ # against that directory and the server dies on startup with
388
+ # "ENOENT: no such file or directory, mkdir './output'". The same value works from a terminal
389
+ # inside the repo, which is why it can look fine in `doctor` and still fail in the app.
390
+ # `doctor` reports an issue for any value that is not a full path.
391
+ # Also: if you run CLI commands (ledger:sync, metrics) from a shell, they read this same .env, so
392
+ # the MCP server and the CLI always land on the same file. If your host's config `env` block
393
+ # sets LEDGER_PATH directly, put the same full path there.
394
+ LEDGER_PATH=
395
+
396
+ # Langfuse dashboards (optional). Fill ALL THREE to turn it on, or leave blank.
397
+ LANGFUSE_PUBLIC_KEY=
398
+ LANGFUSE_SECRET_KEY=
399
+ LANGFUSE_HOST=
400
+
401
+ # ── PROVIDER ATTRIBUTION ────────────────────────────────────────────────────
402
+ # The cloud provider your IDE sends traffic to. Used for per-provider cycle
403
+ # extension metrics when the inbound request carries no recognizable model
404
+ # string (common on llm-gate when CLOUD_MODEL is unset).
405
+ # This is NOT SLM_PROVIDER (which selects ollama vs openai for the local model).
406
+ # Valid values: gemini, claude, chatgpt. Leave blank to skip attribution.
407
+ PROVIDER=
408
+
409
+ # ── ⚠️ SUBSCRIPTION WINDOW BUDGETS — REQUIRED FOR THE CYCLE CARDS ──
410
+ # Paid AI plans give you an allowance that refreshes on a timer called a "usage
411
+ # window" (Claude and Gemini: every 5 hours, ChatGPT: every 3 hours). Each
412
+ # provider gets two dashboard cards showing how much of that window the gate
413
+ # frees up: "Est. Seconds Saved (per prompt)" and "Est. Minutes Saved (total)".
414
+ #
415
+ # SET ALL THREE, even if you only use one provider. If a budget is blank, both
416
+ # of that provider's cards stay EMPTY (show 0). The gate can't find out your
417
+ # plan's limit on its own, so without a budget it reports nothing rather than
418
+ # guess.
419
+ #
420
+ # estimated minutes saved per event = units saved × (window minutes ÷ WINDOW_BUDGET)
421
+ #
422
+ # What a "unit" is depends on how the provider counts usage:
423
+ # CLAUDE / GEMINI → TOKENS per window. Every token saved counts, including
424
+ # tool results shrunk by the slm-gate MCP server.
425
+ # CHATGPT → MESSAGES per window. ChatGPT caps messages, so only a
426
+ # prompt answered fully by the local model saves one. A
427
+ # shrunk prompt still costs one message and adds 0 minutes.
428
+ #
429
+ # ⚠️ THESE ARE ESTIMATES, WITHIN A MARGIN OF ERROR. Anthropic, OpenAI and Google
430
+ # do not publish these limits (only multipliers such as "Max 5x = 5x Pro"), so the
431
+ # minutes shown are estimates too. Pick the value for your plan:
432
+ # Claude (tokens/5h) : Pro ~460000 · Max 5x ~2300000 · Max 20x ~9200000
433
+ # (Max 5x measured 2026-09-16 from Claude Code usage logs
434
+ # against the /usage session meter; Pro and 20x scaled)
435
+ # ChatGPT (messages/3h): Plus ~160 (third-party reports, 2026)
436
+ # Gemini (tokens/5h) : AI Pro ~500000 · Ultra ~10000000
437
+ # (best guess: AI Pro assumed close to Claude Pro;
438
+ # Ultra = 20x AI Pro per Google's plan page)
439
+ # A better number for YOUR account: tokens sent in a window ÷ share of the window
440
+ # used (Claude Code shows the share with /usage). Restart the gate / MCP server
441
+ # after changing these; they are read once at startup. (Most plans also have a
442
+ # weekly cap; this metric only models the short refresh window.)
443
+ CLAUDE_WINDOW_BUDGET=2300000 # tokens per 5-hour window (Claude Max 5x, estimate)
444
+ CHATGPT_WINDOW_BUDGET=160 # messages per 3-hour window (ChatGPT Plus, estimate)
445
+ GEMINI_WINDOW_BUDGET=10000000 # tokens per 5-hour window (Gemini Ultra, estimate)
446
+
447
+ # ── SUBSCRIPTION PLAN (window length) ───────────────────────────────────────
448
+ # Rather than setting individual window minutes overrides, you can set a single
449
+ # SUBSCRIPTION_PLAN. It sets the window LENGTH only. It does NOT set the window
450
+ # budgets above; those must be set separately.
451
+ #
452
+ # Valid values: claude-pro, claude-max-5x, claude-max-20x, chatgpt-go,
453
+ # chatgpt-plus, chatgpt-pro-5x, chatgpt-pro-20x, gemini-plus,
454
+ # gemini-pro, gemini-ultra.
455
+ #
456
+ # Sources (verified 2026-09-09; claude link updated 2026-09-16):
457
+ # claude : https://support.claude.com/en/articles/11049741-what-is-the-max-plan
458
+ # chatgpt : https://help.openai.com (search "ChatGPT usage limits")
459
+ # gemini : https://support.google.com/gemini/answer/16275805
460
+ SUBSCRIPTION_PLAN=claude-max-5x
461
+
462
+ # ============================================================
463
+ # STEP 6 optional extras
464
+ # ============================================================
465
+
466
+ # Let mcp-gate make an EXTRA call to the API MODEL (STEP 3) to clear up an unclear request.
467
+ # This spends metered, per-token API dollars SEPARATE from (on top of) your subscription
468
+ # and each call is logged in the ledger. Needs STEP 3 filled. off = never (default).
469
+ RESOLVER_CLOUD_TIER=off
470
+
471
+ # Cap on total API dollars for the setting above. 0 = never spend.
472
+ RESOLVER_CLOUD_BUDGET_USD=0
473
+
474
+ # Bump this string (v1 -> v2 ...) whenever you change the prompt logic, to clear the cache.
475
+ # It is part of both cache keys (prompt conditioning and tool-output distillation), so changing
476
+ # it invalidates every previously cached result. Bump it after you change prompts, swap either
477
+ # SLM model, or pull a version of slm-gate whose release notes say to.
478
+ PROMPT_VERSION=v1
479
+
480
+ # Shortcut that picks default models for your RAM. Your explicit SLM_*_MODEL above win.
481
+ # ram-16 | ram-24 | ram-32 | ram-48 | ram-64 | ram-128 | custom
482
+ # slm-gate init picks it from your RAM: a size between two presets gets the smaller one, and more than
483
+ # 128 GB gets custom. Models per preset: https://github.com/zenithfoundry/slm-gate/blob/main/docs/prerequisites-and-hardware.md
484
+ # To choose other models that fit your computer: https://github.com/AlexsJones/llmfit
485
+ RAM_PRESET=ram-24
486
+
487
+ # tech-lead-stack integration. Adds TLS-specific handling (preserves TLS's skill gates, feeds
488
+ # TLS's own cost report). It ONLY does anything when mcp-gate is proxying TLS.
489
+ # RULE: if this is `on`, DOWNSTREAM_MCP (STEP 4) MUST point at the TLS server.
490
+ # `on` + blank DOWNSTREAM_MCP is invalid — there is nothing for the adapter to attach to.
491
+ # Not using tech-lead-stack -> off.
492
+ TLS_ADAPTER=on
493
+
494
+ # ── PROTECTING CRITICAL TEXT: which lines the local model must NEVER summarize ──
495
+ # When the proxy shrinks a large result (like a long log or a big file), it automatically
496
+ # recognizes and protects structured information like Markdown tables, code blocks,
497
+ # and headers from being summarized or deleted.
498
+ #
499
+ # However, you might want to protect specific sentences or keywords from ever being
500
+ # summarized. For example, you might want to protect any line containing the word "DEADLINE".
501
+ # The built-in defaults already protect common critical words like MUST, SHALL, and URLs.
502
+
503
+ # Optional: You can provide a custom JSON file that lists extra text patterns you want to protect.
504
+ # Format of the file: { "mode": "extend", "patterns": ["^## Acceptance Criteria", "\\bDEADLINE\\b"] }
505
+ # Why a separate file?: We require you to put these rules in a separate file rather than
506
+ # typing them directly here. This prevents confusing formatting errors and keeps this file clean.
507
+ # Example: "configs/preserve/tls.json" is used for the tech-lead-stack framework. You can
508
+ # change this path to point to any custom JSON file you create.
509
+ # Leave this blank if you only want to use the built-in defaults.
510
+ DISTILL_PRESERVE_PATH=
511
+
512
+ # How should your custom patterns combine with the built-in defaults?
513
+ # extend = Your custom patterns are ADDED to our built-in defaults (recommended).
514
+ # replace = ONLY your custom patterns are used — the built-in defaults are completely ignored.
515
+ DISTILL_PRESERVE_MODE=extend
516
+
517
+ # Turn off summarization entirely for known "skill" or instruction files.
518
+ # Why: Summarizing a file that contains strict rules (like "never push code without asking")
519
+ # might accidentally cause the local model to forget or change those rules.
520
+ # Tradeoff: Turning this off guarantees your rules are kept 100% untouched, but
521
+ # it will use up more of your context window if the instruction file is very large.
522
+ # Default: off (this means summarization is disabled for skill files)
523
+ DISTILL_SKILLS=off
524
+
525
+ # ── SMART MEMORY: The Adaptive Semantic Feedback Loop ──────────────────────
526
+ # The proxy learns from your actions! If the local model summarizes a text and you
527
+ # subsequently ask the agent to "read the hidden lines", the proxy memorizes the meaning
528
+ # of those missing lines.
529
+ # In the future, if the proxy sees a line that means the exact same thing as something
530
+ # you previously had to uncover, it will PREEMPTIVELY protect it from being summarized!
531
+
532
+ # Turn this smart memory learning on/off.
533
+ # off = Rely only on the automatic structure and text pattern rules above.
534
+ # on = Actively compare new text against the memory of your past expansions.
535
+ DISTILL_ADAPTIVE=on
536
+
537
+ # How similar does a new line need to be to your past expansions to trigger protection?
538
+ # 0.86 = Very similar meaning (recommended default).
539
+ # Higher (e.g., 0.95) = Stricter. Protects only near-exact matches.
540
+ # Lower (e.g., 0.75) = Looser. Protects lines that share a similar theme or topic.
541
+ DISTILL_ADAPTIVE_THRESHOLD=0.86
542
+
543
+ # To prevent this smart memory from slowing down your computer by checking every single line,
544
+ # we only check a random percentage of the unprotected lines against the memory database.
545
+ # 0.15 = 15% of unprotected lines are checked against memory.
546
+ DISTILL_ADAPTIVE_EXPLORE_RATE=0.15
547
+
548
+ # ── SUMMARIZATION LIMITS & SIZES ───────────────────────────────────────────
549
+ # When a tool returns a massive amount of text (like a massive log file), the proxy shrinks
550
+ # it using the local model BEFORE it reaches your editor. This ensures you don't waste your
551
+ # expensive cloud tokens on useless text. The original, full text is secretly saved locally
552
+ # so your agent can cheaply recover anything that was trimmed if it needs it later.
553
+ #
554
+ # A quick guide to "tokens": roughly 4 characters is 1 token.
555
+ # As a rule of thumb, 1,000 tokens is about 750 words or 100 lines of code.
556
+
557
+ # MAXIMUM LIMIT. The largest size a SINGLE tool result is allowed to be AFTER shrinking.
558
+ # This is a safety net to stop massive text dumps from crashing your context window.
559
+ # If a result is still larger than this number after being shrunk, the extra text is cut off
560
+ # entirely and replaced with a marker the agent can use to expand it on demand.
561
+ # Lower it = saves more tokens (but causes more "expand" requests).
562
+ # Raise it = keeps more context visible at once (but costs more tokens).
563
+ # NOTE: this is also the point at which the local model is asked to summarize at all. In
564
+ # STANDALONE mode (DOWNSTREAM_MCP blank) it is the ONLY trigger, because the deterministic
565
+ # per-tool heuristics need a tool name that standalone calls do not carry. Shipping 8000 here
566
+ # meant nothing under ~28,000 characters was ever compressed. Matches the code default.
567
+ DISTILL_MAX_TOKENS=2000
568
+
569
+ # MINIMUM LIMIT. Any tool result SMALLER than this number is ignored and left completely untouched.
570
+ # Compressing tiny outputs is a waste of time and risks changing important small details
571
+ # (like mangling a single filename) to save almost nothing.
572
+ # Lower it = shrinks more aggressively.
573
+ # Raise it = leaves more small results alone.
574
+ # Matches the code default and the README. (Both thresholds previously diverged from both.)
575
+ DISTILL_MIN_TOKENS=500
576
+
577
+ # How long (ms) the model gate may hold one request while it shrinks that request's new tool
578
+ # results. Past it, the result is sent as it was (and stays that way for the rest of the
579
+ # conversation, so the provider's prompt cache keeps working). The request is never blocked.
580
+ DISTILL_BUDGET_MS=3000
581
+
582
+ # The proxy always keeps the last few tool results completely FULL and un-shrunk, because
583
+ # whatever the agent just fetched is almost certainly still needed for its very next step.
584
+ # Only OLDER tool results become candidates for shrinking.
585
+ # Higher = safer (keeps your immediate history completely intact) but uses more context window.
586
+ KEEP_RECENT_TOOL_TURNS=4
587
+
588
+ # ── Elision registry (the "get it back cheaply" safety net) ────────────────
589
+ # Whenever the gate shrinks or drops a tool result, it stashes the ORIGINAL in a
590
+ # small local SQLite table so the agent can retrieve exactly what was trimmed
591
+ # without re-running the tool or re-reading the whole file. These three settings
592
+ # bound that stash. (It only ever holds trimmed tool outputs — never your cost or
593
+ # analytics data.)
594
+
595
+ # Max number of stashed originals to keep. Past this, the oldest are deleted.
596
+ # Higher = more originals available for cheap re-fetch (more disk used); lower =
597
+ # less disk, but re-fetching an evicted original falls back to re-running the
598
+ # original tool.
599
+ ELISION_MAX_ENTRIES=5000
600
+
601
+ # How long a stashed original is kept before automatic deletion. Old ones are
602
+ # removed on startup and when next looked up.
603
+ # 180 = ~6 months. In practice a stashed original is almost always re-fetched
604
+ # within minutes, so this is deliberately generous — the size cap below usually
605
+ # kicks in first. Lower it (e.g. 30) if you'd rather rely mainly on the size cap.
606
+ ELISION_RETENTION_DAYS=180
607
+
608
+ # Disk budget for the stash, in megabytes. When exceeded, the least-recently-used
609
+ # originals are deleted first. This is usually the limit that actually bites (it
610
+ # triggers before the 6-month age above).
611
+ ELISION_MAX_MB=500
612
+
613
+ # ── DYNAMIC ROUTING TUNING ─────────────────────────────────────────────────
614
+ # The gate can answer a request two ways: the FREE local model, or the PAID cloud
615
+ # model. It tries local first, a verifier checks the answer, and it falls back to
616
+ # cloud only if the local answer isn't good enough. These settings let the gate
617
+ # LEARN which KINDS of requests the local model is actually good at, so it stops
618
+ # wasting local attempts on kinds it usually fails and sends those straight to
619
+ # cloud. (Categories that improve still get re-tested — see the explore rate.)
620
+
621
+ # Turn the adaptive learning on/off. off = always try local first, regardless of
622
+ # past results. on = skip local for categories the local model has been failing.
623
+ ROUTING_TUNE=off
624
+
625
+ # How many recent requests (per category) to look back over when measuring the
626
+ # local model's success rate.
627
+ # Larger = a smoother, slower-to-change picture; smaller = adapts faster but is
628
+ # noisier (a couple of flukes swing it more).
629
+ ROUTING_TUNE_WINDOW=20
630
+
631
+ # The minimum number of past requests needed for a category before the gate is
632
+ # allowed to act on that category's success rate. Stops it from deciding "local
633
+ # is bad at this" off one or two data points.
634
+ ROUTING_TUNE_MIN_SAMPLES=8
635
+
636
+ # The local success rate BELOW which the gate stops trying local for a category
637
+ # and escalates straight to cloud.
638
+ # 0.5 = if the local model succeeds less than half the time for this kind of
639
+ # request, skip it and go to cloud. Higher (e.g. 0.7) = stricter, escalates more
640
+ # to cloud (more reliable, costs more); lower (e.g. 0.3) = keeps trying local
641
+ # (cheaper, but more failed local attempts that then escalate).
642
+ ROUTING_TUNE_THRESHOLD=0.5
643
+
644
+ # Even for categories it has learned to skip, the gate deliberately tries local
645
+ # this fraction of the time, to keep learning (a category might have improved, or
646
+ # was judged on stale data). This is the classic "explore vs exploit" knob.
647
+ # 0.15 = explore ~15% of the time. Higher = adapts faster but runs more risky
648
+ # trials; lower = more conservative, adapts more slowly.
649
+ ROUTING_TUNE_EXPLORE_RATE=0.15
650
+
651
+ # ============================================================
652
+ # STEP 7 downstream multi-agent configuration (OPTIONAL)
653
+ # ============================================================
654
+ #
655
+ # These variables configure the agents in downstream orchestration
656
+ # frameworks (like tech-lead-stack).
657
+ # If you are not using a multi-agent framework or prefer to use
658
+ # its defaults, leave these entirely BLANK.
659
+
660
+ # The reasoning-heavy model used for architecture and planning.
661
+ # MODEL_PLANNER=claude-opus-4-6
662
+
663
+ # The fast, high-context model used for writing code.
664
+ # MODEL_IMPLEMENTER=gemini-3.5-flash
665
+
666
+ # The high-precision model used for reviewing and grading plans.
667
+ # MODEL_AUDITOR=claude-sonnet-4-6
668
+
669
+ # The model used for final decisions in cross-model verification.
670
+ # MODEL_ADJUDICATOR=claude-sonnet-4-6
671
+
672
+ # Downstream API Keys (if different from CLOUD_API_KEY in STEP 3)
673
+ # ANTHROPIC_API_KEY=sk-ant-...
674
+ # GEMINI_API_KEY=...