Configuration

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.

on this page

Schema

{
  "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"
  }
}

Backends

KindWhat it speaksAuth
ollamaOllama HTTP API (/api/chat, /api/embeddings)none
openai-compatibleOpenAI Chat Completions + Embeddings shape (works with OpenAI, Together, Groq, OpenRouter, ...)apiKeyEnv (env-var name) or apiKey (inline)
anthropic(placeholder)not yet implemented
API keys: 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" }
  }
}

Agent specs

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.

Model picks — M-series Mac, 24 GB

RolePickWhy
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.

Hardware budgets

RAM budgetSuggested classifierSuggested extractorNotes
8 GBphi4-mini:3.8b(skip — use classifier for everything)Quality suffers but the pipeline runs.
16 GBqwen3:4b-instructllama3.1:8b or qwen2.5:7bAcceptable on most workloads.
24 GB (M-series)qwen3:4b-instructqwen2.5:14bRecommended default.
32 GB+qwen3:4b-instructqwen2.5:32b or remote frontierBest local quality.

Frontier-quality routing

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.

Transcript roots

If your AI agent stores transcripts in a non-default location, override the root:

{
  "transcriptRoots": {
    "cursor":     "/Volumes/data/cursor-projects",
    "claudeCode": "~/work/.claude/projects"
  }
}

Cache invalidation

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:

Use codegps verify --invalidate-triage-days 30 to invalidate triage runs older than 30 days so the next ingest re-triages stale windows.