Per-agent model selection lives in JSON. Global defaults at
~/.codegps/config.json; per-project overrides at
<project>/.codegps/config.json. Per-project values
deep-merge over global.
{
"concurrency": 8, // max concurrent agent (LLM) calls; 1 = serial
"agentBackends": {
"<name>": { "kind": "ollama|openai-compatible|anthropic",
"endpoint": "<url>",
"apiKeyEnv": "<ENV_VAR>", // name of the env var holding the key
"apiKey": "<key>" } // OR an inline key (plaintext)
},
"agents": {
"<agent-name>": {
"model": "<backend>:<model>",
"fallback": "<backend>:<model>",
"windowTokens": 2000
}
},
"research": { "kind": "none" }, // or "search-api" (opt-in external lookups)
"transcriptRoots": {
"cursor": "~/.cursor/projects",
"claudeCode": "~/.claude/projects",
"codex": "~/.codex/sessions"
}
}
| Kind | What it speaks | Auth |
|---|---|---|
ollama | Ollama HTTP API (/api/chat, /api/embeddings) | none |
openai-compatible | OpenAI Chat Completions + Embeddings shape (works with OpenAI, Together, Groq, OpenRouter, ...) | apiKeyEnv (env-var name) or apiKey (inline) |
anthropic | (placeholder) | not yet implemented |
apiKeyEnv vs apiKey.
apiKeyEnv is the name of an environment variable, not the
key — the backend reads process.env[apiKeyEnv]. Pasting a raw key
(sk-…) into apiKeyEnv resolves to an undefined env
var and fails with "requires apiKey". To paste a key directly, use the
apiKey field. Prefer apiKeyEnv to keep secrets out
of the config file.
Backends are referenced from agent specs by name:
{
"agentBackends": {
"local": { "kind": "ollama", "endpoint": "http://localhost:11434" },
"openrouter": { "kind": "openai-compatible",
"endpoint": "https://openrouter.ai/api/v1",
"apiKeyEnv": "OPENROUTER_API_KEY" }
}
}
Each agent gets one spec. The model string is "<backend>:<model>"; the first colon splits, so model names with colons (e.g. llama3.1:8b) are fine.
{
"agents": {
"triage": { "model": "default:qwen3:4b-instruct", "windowTokens": 2000 },
"dedupe": { "model": "default:qwen3-embedding:0.6b" },
"decision": { "model": "default:qwen2.5:14b" },
"businessLogic": { "model": "default:qwen2.5:14b" },
"intent": { "model": "default:qwen3:4b-instruct" },
"problemSolution": { "model": "default:qwen2.5:14b" },
"clusterer": { "model": "default:qwen3:4b-instruct" },
"summarizer": { "model": "default:qwen2.5:14b" },
"technicalProfiler": { "model": "default:qwen2.5:14b" },
"domainModeler": { "model": "default:qwen2.5:14b" },
"industryClassifier": { "model": "default:qwen2.5:14b" },
"industryEnricher": { "model": "default:qwen2.5:14b" },
"linker": { "model": "default:qwen2.5:14b" },
"verifier": { "model": "default:qwen2.5:14b" },
"skillSynthesizer": { "model": "default:qwen2.5:14b" }
}
}
The L2.5 enrichment agents — technicalProfiler,
domainModeler, industryClassifier,
industryEnricher, domainAnalyzer — plus the
code-analysis agents (fileAnalyzer,
architectureAnalyzer) and the global profileWriter
are the second-brain additions. Route them to a frontier backend for best
results; they read across the whole project, not single windows.
| Role | Pick | Why |
|---|---|---|
| Classifier (triage, intent, clusterer) | qwen3:4b-instruct~2.5 GB |
Top of MTEB-adjacent JSON-strict benchmarks at 4B; cheap, fast. |
| Extractor (decision, businessLogic, summarizer, linker, verifier) | qwen2.5:14b~9 GB Q4 |
Strongest structured extraction at this scale (≈ 94% vs ≈ 87% Llama on entity-extraction tests). |
| Embeddings (dedupe) | qwen3-embedding:0.6b~1 GB |
#1 on MTEB at this size, 32K context. |
Combined hot footprint ≈ 12.5 GB. Leaves ~10 GB headroom on a 24 GB machine for macOS, Cursor, browser.
| RAM budget | Suggested classifier | Suggested extractor | Notes |
|---|---|---|---|
| 8 GB | phi4-mini:3.8b | (skip — use classifier for everything) | Quality suffers but the pipeline runs. |
| 16 GB | qwen3:4b-instruct | llama3.1:8b or qwen2.5:7b | Acceptable on most workloads. |
| 24 GB (M-series) | qwen3:4b-instruct | qwen2.5:14b | Recommended default. |
| 32 GB+ | qwen3:4b-instruct | qwen2.5:32b or remote frontier | Best local quality. |
Add a second backend and route the agents that benefit most from a strong
model to it — extractors and the L2.5 enrichment agents — while embeddings
and cheap classifiers stay local. A full example ships as
frontier.config.example.json in the repo.
{
"concurrency": 8,
"agentBackends": {
"local": { "kind": "ollama", "endpoint": "http://localhost:11434" },
"openrouter": { "kind": "openai-compatible",
"endpoint": "https://openrouter.ai/api/v1",
"apiKeyEnv": "OPENROUTER_API_KEY" }
},
"agents": {
"dedupe": { "model": "local:nomic-embed-text" },
"triage": { "model": "openrouter:google/gemini-2.5-flash" },
"intent": { "model": "openrouter:google/gemini-2.5-flash" },
"clusterer": { "model": "openrouter:google/gemini-2.5-flash" },
"summarizer": { "model": "openrouter:google/gemini-2.5-flash" },
"decision": { "model": "openrouter:anthropic/claude-sonnet-4" },
"businessLogic": { "model": "openrouter:anthropic/claude-sonnet-4" },
"problemSolution": { "model": "openrouter:anthropic/claude-sonnet-4" },
"technicalProfiler": { "model": "openrouter:anthropic/claude-sonnet-4" },
"domainModeler": { "model": "openrouter:anthropic/claude-sonnet-4" },
"industryClassifier": { "model": "openrouter:anthropic/claude-sonnet-4" },
"industryEnricher": { "model": "openrouter:anthropic/claude-sonnet-4" },
"linker": { "model": "openrouter:anthropic/claude-sonnet-4" },
"verifier": { "model": "openrouter:anthropic/claude-sonnet-4" },
"skillSynthesizer": { "model": "openrouter:anthropic/claude-sonnet-4" }
}
}
The fallback field is reserved for runtime fallback on backend
errors. If agents return nothing, check agent_runs for errors
(a wrong model slug 400s; a missing/misplaced key fails with "requires
apiKey") — see the key gotcha above.
If your AI agent stores transcripts in a non-default location, override the root:
{
"transcriptRoots": {
"cursor": "/Volumes/data/cursor-projects",
"claudeCode": "~/work/.claude/projects"
}
}
Agent runs are cached in knowledge.db / agent_runs by
(agent.name, model, input_hash, prompt_version). To force
a re-run for an agent:
promptVersion in the agent source — invalidates the cache for that agent on next ingest.DELETE FROM agent_runs WHERE agent_name='triage';
Use codegps verify --invalidate-triage-days 30 to invalidate
triage runs older than 30 days so the next ingest re-triages stale
windows.