Skip to content

Configuration

Cograph is configured in two places, and knowing which is which saves an hour of confusion:

  • Deployment settings — database URLs, secrets, timeouts, retrieval tuning — come from environment variables or a YAML file.
  • Runtime settings — which model does what, OIDC providers, git credentials, the agent briefing — live in the database and are managed in the admin UI.

The second list is not an oversight. See what lives in the database.

The environment contract

Every setting is nested under a group, and the environment-variable name is built from it:

COGRAPH_<GROUP>__<FIELD>          # note the DOUBLE underscore
bash
COGRAPH_DATABASE__URL='postgresql+asyncpg://…'
COGRAPH_EMBEDDING__API_KEY='sk-…'
COGRAPH_RETRIEVAL__RERANK__PROVIDER='local_cross_encoder'   # nests twice
COGRAPH_MCP__ALLOWED_HOSTS='["cograph.example.com"]'        # lists are JSON

Top-level settings have no group: COGRAPH_APP_NAME, COGRAPH_ENVIRONMENT, COGRAPH_VERSION, COGRAPH_API_PREFIX.

Precedence

Highest wins:

  1. Values passed in code (tests only)
  2. Environment variables
  3. YAML file at $COGRAPH_CONFIG_FILE, default ./config.yaml, used only if the file exists

WARNING

Environment beats YAML. If an edit to config.yaml appears to do nothing, check whether Compose or your Helm secret is already exporting the same variable.

Settings that fail startup

Better to know these before a deploy than during one.

ConditionResult
environment=production and auth.jwt_secret is empty, a known placeholder, or under 32 charactersValueError at boot
embedding.dimensions is anything other than 1536ValueError at boot
embedding.enabled=true with an empty api_keyValueError at boot
completion.enabled=true with an empty api_keyValueError at boot
environment=production, Redis unreachable, and redis.allow_in_memory_rate_limit_fallback=falseRuntimeError at boot

In production, secure_cookies is forced on regardless of the configured value.

Reference

Top level

SettingEnvDefault
app_nameCOGRAPH_APP_NAMECograph
environmentCOGRAPH_ENVIRONMENTdevelopment — one of development, testing, production
versionCOGRAPH_VERSION0.1.0
api_prefixCOGRAPH_API_PREFIX/api

environment does more than label things: it gates whether the OpenAPI schema is served, whether cookies are forced secure, and whether Redis is mandatory.

database

SettingEnv suffixDefault
urlDATABASE__URLpostgresql+asyncpg://postgres:postgres@localhost:5432/cograph
echoDATABASE__ECHOfalse — SQL statement logging

The driver must be asyncpg. Migrations use the same URL.

redis

SettingEnv suffixDefault
urlREDIS__URLredis://localhost:6379/0
allow_in_memory_rate_limit_fallbackREDIS__ALLOW_IN_MEMORY_RATE_LIMIT_FALLBACKfalse

The fallback flag only matters in production, where Redis-backed rate limiting is mandatory: with the flag off, an unreachable Redis fails the boot rather than silently degrading to per-process counters that a multi-replica deployment could trivially bypass. Development and testing always use the in-memory limiter.

git

SettingEnv suffixDefault
checkouts_rootGIT__CHECKOUTS_ROOT.cograph/checkouts

Must be writable by both the backend and the worker, and must be the same path for both.

archive_upload

Guards for the zip-upload path, sized to stop a zip bomb from exhausting the disk.

SettingEnv suffixDefault
max_compressed_bytesARCHIVE_UPLOAD__MAX_COMPRESSED_BYTES209715200 (200 MiB)
max_decompressed_bytesARCHIVE_UPLOAD__MAX_DECOMPRESSED_BYTES1073741824 (1 GiB)
max_per_file_bytesARCHIVE_UPLOAD__MAX_PER_FILE_BYTES52428800 (50 MiB)
max_inflation_ratioARCHIVE_UPLOAD__MAX_INFLATION_RATIO100.0
max_entriesARCHIVE_UPLOAD__MAX_ENTRIES200000

The compressed cap is mirrored by nginx's client_max_body_size 200m. Raising one without the other gets you a 413 from the proxy.

auth

SettingEnv suffixDefault
jwt_secretAUTH__JWT_SECRETdev-secret-change-memust be replaced in production
jwt_algorithmAUTH__JWT_ALGORITHMHS256
access_token_ttl_secondsAUTH__ACCESS_TOKEN_TTL_SECONDS28800 (8 h)
refresh_token_ttl_secondsAUTH__REFRESH_TOKEN_TTL_SECONDS2592000 (30 d)
access_cookie_nameAUTH__ACCESS_COOKIE_NAMEcograph_access
refresh_cookie_nameAUTH__REFRESH_COOKIE_NAMEcograph_refresh
csrf_cookie_nameAUTH__CSRF_COOKIE_NAMEcograph_csrf
registration_enabledAUTH__REGISTRATION_ENABLEDfalse
public_readAUTH__PUBLIC_READfalse
secure_cookiesAUTH__SECURE_COOKIESfalse — forced true in production
external_urlAUTH__EXTERNAL_URLnull
oidc_state_ttl_secondsAUTH__OIDC_STATE_TTL_SECONDS600
llm_encryption_secretAUTH__LLM_ENCRYPTION_SECRETnull
oidc_encryption_secretAUTH__OIDC_ENCRYPTION_SECRETnull

public_read is the switch for anonymous browsing, and it only ever exposes repositories whose visibility is public.

external_url pins the public origin used to build OIDC redirect_uri values. Set it whenever Cograph sits behind a proxy that rewrites the host, or the provider's exact-match check will reject the callback.

The two encryption secrets are covered under secret rotation.

cors

SettingEnv suffixDefault
allowed_originsCORS__ALLOWED_ORIGINS["http://localhost:5173","http://localhost:3000"]

Only relevant when a browser app on another origin calls the API. The bundled SPA is same-origin through nginx and needs nothing here.

logging

SettingEnv suffixDefault
levelLOGGING__LEVELINFODEBUG/INFO/WARNING/ERROR/CRITICAL
formatLOGGING__FORMATtext — or json
access_logLOGGING__ACCESS_LOGtrue

Use json wherever logs are shipped to a collector.

embedding

SettingEnv suffixDefault
enabledEMBEDDING__ENABLEDfalse
api_urlEMBEDDING__API_URLhttps://api.openai.com/v1
api_keyEMBEDDING__API_KEYempty
modelEMBEDDING__MODELtext-embedding-3-small
dimensionsEMBEDDING__DIMENSIONS1536validated, no other value accepted
batch_sizeEMBEDDING__BATCH_SIZE256
request_timeout_secondsEMBEDDING__REQUEST_TIMEOUT_SECONDS120.0
connect_timeout_secondsEMBEDDING__CONNECT_TIMEOUT_SECONDS10.0

Where these still matter

Both the indexing pipeline and the query path take their embedding provider — including the API key — from the database. What is still read from this group is batch_size, dimensions and the two timeouts.

So COGRAPH_EMBEDDING__API_KEY is not a second credential you need to keep working: it is consumed by the CLI paths and remains for legacy compatibility. Setting it does no harm; setting it instead of the database assignment does not work.

The timeouts exist because a stalled endpoint without a client timeout hangs the step until the two-hour job deadline.

completion

SettingEnv suffixDefault
enabledCOMPLETION__ENABLEDfalse
preview_enabledCOMPLETION__PREVIEW_ENABLEDtrue
api_urlCOMPLETION__API_URLhttps://api.openai.com/v1
api_keyCOMPLETION__API_KEYempty
modelCOMPLETION__MODELgpt-5.4-mini
request_timeout_secondsCOMPLETION__REQUEST_TIMEOUT_SECONDS120.0
connect_timeout_secondsCOMPLETION__CONNECT_TIMEOUT_SECONDS10.0

The timeouts here apply to the wiki and summary providers even in database mode.

retrieval

SettingEnv suffixDefault
rrf_kRETRIEVAL__RRF_K60
candidate_capRETRIEVAL__CANDIDATE_CAP300
rerank.enabledRETRIEVAL__RERANK__ENABLEDtrue
rerank.thresholdRETRIEVAL__RERANK__THRESHOLD50
rerank.providerRETRIEVAL__RERANK__PROVIDERdisabled
rerank.modelRETRIEVAL__RERANK__MODELcross-encoder/ms-marco-MiniLM-L-6-v2

rrf_k is the reciprocal-rank-fusion constant; candidate_cap bounds how many candidates each stream contributes before fusion. rerank.threshold is the candidate count below which reranking is skipped as not worth the latency.

Only two rerank providers actually work

provider accepts disabled, local_cross_encoder, cohere, voyage and jina, but only the first two are implemented — the rest raise NotImplementedError. local_cross_encoder additionally needs the [reranker-local] extra (roughly 500 MB of torch, deliberately excluded from the base image) and reads COHERE_API_KEY / VOYAGE_API_KEY as plain, unprefixed environment variables for the hosted providers.

A reranker that fails to construct degrades silently to no reranking, with a log warning. Check the log rather than assuming it is active.

pipeline_timeouts

Per-step deadlines, all 3600 seconds by default. These are ceilings, not targets — typical steps finish well inside them.

SettingEnv suffix
parse_secondsPIPELINE_TIMEOUTS__PARSE_SECONDS
extract_graph_secondsPIPELINE_TIMEOUTS__EXTRACT_GRAPH_SECONDS
embed_secondsPIPELINE_TIMEOUTS__EMBED_SECONDS
index_repo_docs_secondsPIPELINE_TIMEOUTS__INDEX_REPO_DOCS_SECONDS
embed_repo_docs_secondsPIPELINE_TIMEOUTS__EMBED_REPO_DOCS_SECONDS
generate_summaries_secondsPIPELINE_TIMEOUTS__GENERATE_SUMMARIES_SECONDS
generate_wiki_secondsPIPELINE_TIMEOUTS__GENERATE_WIKI_SECONDS

Plus the sweep that recovers runs abandoned by a dead worker:

SettingEnv suffixDefault
stale_run_threshold_minutesPIPELINE_TIMEOUTS__STALE_RUN_THRESHOLD_MINUTES15
stale_run_sweep_limitPIPELINE_TIMEOUTS__STALE_RUN_SWEEP_LIMIT50

query_log

SettingEnv suffixDefault
disabledQUERY_LOG__DISABLEDfalse
query_text_max_bytesQUERY_LOG__QUERY_TEXT_MAX_BYTES200 (16–4096)
retention_daysQUERY_LOG__RETENTION_DAYS30 (1–365)
repo_flag_cache_ttl_secondsQUERY_LOG__REPO_FLAG_CACHE_TTL_SECONDS30 (0–3600)

disabled is a kill switch that takes effect without a redeploy. Query text is truncated on a UTF-8 boundary before insert and flagged as truncated, so the full text is never retained. Retention is enforced by a daily prune job.

mcp

SettingEnv suffixDefault
allowed_hostsMCP__ALLOWED_HOSTS[]
allowed_originsMCP__ALLOWED_ORIGINS[]
briefing_max_lengthMCP__BRIEFING_MAX_LENGTH8000 (256–32768)

Setting a non-empty allowed_hosts automatically enables DNS-rebinding protection on the MCP transport. Do set it for an internet-facing deployment.

Variables outside the COGRAPH_<GROUP>__ scheme

VariablePurpose
COGRAPH_CONFIG_FILEPath to the YAML config. Default ./config.yaml.
COGRAPH_BOOTSTRAP_TOKEN_FILEWhere the first-admin token is written. Default ./.cograph/bootstrap.token.
COGRAPH_ADMIN_PASSWORDRead by create-admin / reset-password when --password is omitted.
COHERE_API_KEY, VOYAGE_API_KEYHosted rerank providers. Note: no COGRAPH_ prefix.

What lives in the database

These are managed through the admin UI and API, not through environment variables, because they are operational state an owner changes at runtime rather than deployment configuration.

LLM roles

Four roles, at most one row each. A code path whose role is unassigned raises LLM_ROLE_UNCONFIGURED (HTTP 503) — there is deliberately no default, so a misconfiguration is loud instead of quietly expensive or quietly wrong.

RoleStatusUsed byConstraints
embeddingRequiredCode and document embedding, query embeddingembedding_dim must be 1536
completion_writerActiveWiki page writing, code summaries
completion_fastReservedNothing yet
completion_reasoningReservedNothing yetreasoning_effort allowed only here

Two roles are reserved, not features

The runtime resolves only embedding and completion_writer. completion_fast and completion_reasoning can be assigned in the admin UI and are validated and stored, but no code path consumes them today — the four-role resolver exists and has no production caller. Assigning them changes nothing; leaving them empty costs nothing.

Provider credentials are stored separately as encrypted secrets (a name, a base URL, an API key) and referenced by the role assignment, so several roles can share one provider.

Managed at /admin?tab=llm-runtime, with a Test action per row.

Everything else

WhatWhere
OIDC identity providers (client secrets encrypted)/admin?tab=identity-providers
Git hosts, clone credentials, webhook secrets (encrypted)/admin?tab=git-hosts
The MCP operator briefing/admin?tab=mcp
SCIM clients and their bearer tokens/admin?tab=scim
Groups and their repository/collection grants/admin?tab=groups
Per-repository sync schedule and query-logging opt-outthe repository's settings

Secret rotation

By default the ciphers protecting LLM provider keys and OIDC client secrets derive their keys from auth.jwt_secret. That couples two unrelated rotations: a JWT-secret change would invalidate stored credentials, and a JWT-secret leak would compromise them.

To decouple, set both independent secrets and re-encrypt:

bash
export COGRAPH_AUTH__LLM_ENCRYPTION_SECRET=''
export COGRAPH_AUTH__OIDC_ENCRYPTION_SECRET=''

python -m backend.app.cli reencrypt-secrets --dry-run   # inspect first
python -m backend.app.cli reencrypt-secrets

The command is idempotent and reads rows still under the legacy key. Production logs a warning at startup while either secret is unset.

Note that git host credentials use their own cipher, derived separately, and are not covered by this rotation.

Not configurable

Worth knowing so you do not go looking. These are compile-time constants:

ValueWhat it is
4Worker job concurrency (tuned down from 10 after an OOM kill)
7200 sWorker job timeout
1Queue retry attempts — no automatic retries, by design
4Wiki page-writing concurrency
0.3Fuzzy symbol-match similarity threshold
20Graph-pivot node cap
25Maximum top_k for the MCP retrieve tool
5, exponential jitterLLM retry attempts and backoff
20 / IP, 5 failed / email, per 15 minLogin rate limits

The example file

config.example.yaml is a starting point, not a complete reference. It covers database, redis, git, auth (a subset), cors, embedding and completion. It does not mention archive_upload, logging, retrieval, pipeline_timeouts, query_log or mcp — for those, use the tables above.

Documents Cograph 0.1.0 · Apache-2.0 · pre-1.0, so APIs and migrations may change.