# Configuration

URL: https://cogitator.app/docs/getting-started/configuration

> Configure Cogitator with YAML files, environment variables, and provider settings.

## Configuration File

Create a `cogitator.yml` in your project root:

```yaml
llm:
  defaultProvider: ollama
  defaultModel: ollama/llama3.2
  providers:
    ollama:
      baseUrl: http://localhost:11434
    openai:
      apiKey: ${OPENAI_API_KEY}
    anthropic:
      apiKey: ${ANTHROPIC_API_KEY}
    google:
      apiKey: ${GOOGLE_API_KEY}

limits:
  maxConcurrentRuns: 10
  defaultTimeout: 120000

memory:
  adapter: redis
  redis:
    url: redis://localhost:6379
  embedding:
    provider: ollama
    model: nomic-embed-text
```

Load it with `@cogitator-ai/config`:

```typescript
import { Cogitator } from '@cogitator-ai/core';
import { loadConfig } from '@cogitator-ai/config';

const config = loadConfig();
const cogitator = new Cogitator(config);
```

`loadConfig()`:

* Reads the first of `cogitator.yaml`, `cogitator.yml`, `.cogitator.yaml`, `.cogitator.yml` in the current working directory (none is fine)
* Substitutes `${VAR}` references in the YAML
* Merges environment variables over the file (see below)
* Merges `overrides` over both
* Validates the result against Zod schemas and throws `Invalid configuration: ...` listing the offending fields

```typescript
const config = loadConfig({
  configPath: './config/cogitator.yml', // must exist, or loadConfig throws
  skipEnv: false, // true: ignore environment variables
  skipYaml: false, // true: ignore config files
  overrides: { limits: { maxConcurrentRuns: 4 } },
});
```

`loadConfig()` does not read `.env` files. Load them first with `process.loadEnvFile()` (Node 20.12+), or parse one with `loadDotenvFile(path)` / `parseDotenv(text)` from the same package.

## Top-Level Sections

| Section       | What it configures                                                                                                        |
| ------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `llm`         | `defaultProvider`, `defaultModel`, `providers`, `retry`, `promptCache`, `plugins`                                         |
| `prompts`     | `autoDeployWinner` for [prompt A/B tests](/docs/advanced/prompt-versions)                                                 |
| `limits`      | `maxConcurrentRuns`, `defaultTimeout` (ms), `maxTokensPerRun` — see [Cogitator](/docs/core/cogitator#defaults-and-limits) |
| `memory`      | Adapter, its connection, `embedding` and `contextBuilder`                                                                 |
| `sandbox`     | Defaults, container pool, Docker and WASM settings for [sandboxed tools](/docs/deployment/sandbox)                        |
| `reflection`  | [Reflection](/docs/advanced/reflection) after tool calls, errors or at the end of a run                                   |
| `guardrails`  | [Constitutional AI](/docs/advanced/constitutional-ai) input/output/tool filtering                                         |
| `costRouting` | [Cost-aware model routing](/docs/advanced/cost-routing) and budgets                                                       |
| `security`    | `promptInjection` detection and `pii` masking ([Security](/docs/advanced/security))                                       |
| `context`     | [Context compression](/docs/advanced/context-management) for long conversations                                           |
| `deploy`      | Target, server, port, registry and resources for `cogitator deploy` ([Deploy](/docs/deployment/deploy-package))           |
| `logging`     | `level`, `format`, `destination`, `filePath` of the process-wide logger (see below)                                       |

### Memory

`memory.adapter` names the thread store, with its settings under the key of the same name. The runtime builds and connects it on the first run (or on `getMemory()`):

| `adapter`  | Settings                                                                                              |
| ---------- | ----------------------------------------------------------------------------------------------------- |
| `memory`   | `inMemory.maxEntries` (optional)                                                                      |
| `redis`    | `redis.url`, or `redis.host` + `redis.port`, or `redis.cluster.nodes`; `keyPrefix`, `ttl`, `password` |
| `postgres` | `postgres.connectionString`, `schema`, `poolSize`                                                     |
| `sqlite`   | `sqlite.path` (`':memory:'` for an in-memory database), `walMode`                                     |
| `mongodb`  | `mongodb.uri`, `database`, `collectionPrefix`                                                         |

When the section lacks what the store needs (for example `adapter: sqlite` without `sqlite.path`), or the connection fails, the runtime logs a warning, runs without memory and tries again on the next run. `adapter: qdrant` only logs a warning (see below). The Postgres store sizes its vector column to the embedding model in `memory.embedding`.

`qdrant` is not a thread store: it holds embeddings. Pick one of the stores above as `adapter` and add `memory.qdrant` next to `memory.embedding` and `memory.contextBuilder`; the context builder then searches Qdrant for semantic context. The runtime only searches it — add embeddings with a `QdrantAdapter` of your own.

```yaml title="cogitator.yml"
memory:
  adapter: redis
  redis:
    host: localhost
    port: 6379
  qdrant:
    url: http://localhost:6333
    collection: cogitator
    dimensions: 768
  embedding:
    provider: ollama
    model: nomic-embed-text
  contextBuilder:
    strategy: hybrid
    includeSemanticContext: true
```

`memory.qdrant.dimensions` must match the embedding model; a mismatch is logged. `memory.embedding.dimensions` sets the vector size: OpenAI `text-embedding-3-*` and Google models return vectors of that size, and for Ollama it declares the size of a model the runtime does not know. `memory.contextBuilder.graphContextOptions` (`maxNodes`, `maxEdges`, `maxDepth`, `includeInferred`, `entityTypes`) shapes the [knowledge-graph context](/docs/memory#context-building). To use an adapter you built yourself, connect it and hand it over:

```typescript
import { Cogitator } from '@cogitator-ai/core';
import { SQLiteAdapter } from '@cogitator-ai/memory';

const memory = new SQLiteAdapter({ provider: 'sqlite', path: './data/memory.db' });
const connected = await memory.connect();
if (!connected.success) throw new Error(connected.error);

const cogitator = new Cogitator({ llm: { defaultModel: 'ollama/llama3.2' } });
cogitator.memory = memory;
```

### Logging

`logging` configures the process-wide logger that `getLogger()` returns, when a `Cogitator` is created with it; with several runtimes, the last one created with `logging` wins. `level` is `debug`, `info` (default), `warn`, `error` or `silent`. `format` is `pretty` (default) or `json`. `destination: file` appends one line per entry to `filePath`, as JSON unless `format` says otherwise; without `filePath`, or where there is no file system (edge runtimes), it logs to the console and warns.

```yaml title="cogitator.yml"
logging:
  level: warn
  destination: file
  filePath: ./logs/cogitator.log
```

Without `logging`, the logger logs at `info` (or `LOG_LEVEL`), as JSON when `NODE_ENV=production` and pretty-printed otherwise. In code, build the same logger with `createLoggerFromConfig()` or your own with `createLogger()`, and install it with `setLogger()`:

```typescript
import { createLogger, createLoggerFromConfig, setLogger } from '@cogitator-ai/core';

setLogger(createLoggerFromConfig({ level: 'debug', destination: 'file', filePath: './debug.log' }));
setLogger(createLogger({ level: 'debug', format: 'json' }));
```

## Environment Variables

Cogitator reads environment variables with the `COGITATOR_` prefix:

| Variable                                                                               | Config path                       |
| -------------------------------------------------------------------------------------- | --------------------------------- |
| `COGITATOR_LLM_DEFAULT_PROVIDER`                                                       | `llm.defaultProvider`             |
| `COGITATOR_LLM_DEFAULT_MODEL`                                                          | `llm.defaultModel`                |
| `COGITATOR_OLLAMA_BASE_URL`, `COGITATOR_OLLAMA_API_KEY`                                | `llm.providers.ollama.*`          |
| `COGITATOR_OPENAI_API_KEY`, `COGITATOR_OPENAI_BASE_URL`                                | `llm.providers.openai.*`          |
| `COGITATOR_ANTHROPIC_API_KEY`                                                          | `llm.providers.anthropic.apiKey`  |
| `COGITATOR_GOOGLE_API_KEY`                                                             | `llm.providers.google.apiKey`     |
| `COGITATOR_AZURE_API_KEY`, `_ENDPOINT`, `_API_VERSION`, `_DEPLOYMENT`                  | `llm.providers.azure.*`           |
| `COGITATOR_BEDROCK_REGION`, `_ACCESS_KEY_ID`, `_SECRET_ACCESS_KEY`                     | `llm.providers.bedrock.*`         |
| `COGITATOR_VLLM_BASE_URL`                                                              | `llm.providers.vllm.baseUrl`      |
| `COGITATOR_MISTRAL_API_KEY`, `_GROQ_API_KEY`, `_TOGETHER_API_KEY`, `_DEEPSEEK_API_KEY` | `llm.providers.<provider>.apiKey` |
| `COGITATOR_LIMITS_MAX_CONCURRENT_RUNS`, `_DEFAULT_TIMEOUT`, `_MAX_TOKENS_PER_RUN`      | `limits.*` (digits only)          |
| `COGITATOR_DEPLOY_TARGET`, `_PORT`, `_REGISTRY`                                        | `deploy.*`                        |

Standard provider variables work too; a `COGITATOR_*` variable wins over its standard counterpart:

```bash
OPENAI_API_KEY=sk-xxx
OPENAI_BASE_URL=https://proxy.example.com/v1
ANTHROPIC_API_KEY=sk-ant-xxx
GOOGLE_API_KEY=xxx            # or GEMINI_API_KEY
OLLAMA_URL=http://localhost:11434   # or OLLAMA_HOST (scheme optional)
OLLAMA_API_KEY=xxx            # Ollama Cloud; URL defaults to https://ollama.com unless set in env or YAML
AZURE_OPENAI_API_KEY=xxx      # with AZURE_OPENAI_ENDPOINT, optional AZURE_OPENAI_DEPLOYMENT
AWS_REGION=us-east-1          # AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY for Bedrock
MISTRAL_API_KEY=xxx
GROQ_API_KEY=xxx
TOGETHER_API_KEY=xxx
DEEPSEEK_API_KEY=xxx
```

Azure is configured from the environment only when both the key and the endpoint are set.

### Referencing Environment Variables in YAML

String values in `cogitator.yml` may reference environment variables. `${VAR}` becomes the value (or an empty string), `${VAR:-default}` falls back when the variable is unset or empty, `${VAR-default}` only when unset, and `$$` is a literal `$`:

```yaml title="cogitator.yml"
llm:
  providers:
    openai:
      apiKey: ${OPENAI_API_KEY}
      baseUrl: ${OPENAI_BASE_URL:-https://api.openai.com/v1}
```

Substituted values are always strings, so numeric fields (for example `deploy.port`) must be written literally.

## Programmatic Configuration

You can skip the YAML file and validate a config object directly:

```typescript
import { Cogitator } from '@cogitator-ai/core';
import { defineConfig } from '@cogitator-ai/config';

const config = defineConfig({
  llm: {
    defaultModel: 'openai/gpt-6.1-sol',
    providers: {
      openai: {
        apiKey: process.env.OPENAI_API_KEY!,
      },
    },
  },
  memory: {
    adapter: 'postgres',
    postgres: {
      connectionString: process.env.DATABASE_URL!,
    },
    embedding: {
      provider: 'openai',
      apiKey: process.env.OPENAI_API_KEY!,
      model: 'text-embedding-3-small',
    },
  },
});

const cogitator = new Cogitator(config);
```

`defineConfig()` validates like `loadConfig()` but reads neither files nor the environment. Neither of them is required: `new Cogitator({...})` takes the same object without validation.

Options that hold functions, stores or backends exist only in code: `llm.backends`, `llm.retry.onRetry`, `runCheckpoints`, `prompts.versions` / `prompts.abTests` / `prompts.score`, `security.promptInjection.llmBackend`, and callbacks such as `security.promptInjection.onThreat`, `security.pii.onDetect` and `guardrails.onToolApproval`. Plain-data options, including `llm.plugins` and `prompts.autoDeployWinner`, can come from YAML. Add the code-only options next to the loaded config:

```typescript
const config = loadConfig();
const cogitator = new Cogitator({
  ...config,
  prompts: { ...config.prompts, score: (result) => (result.output.length > 0 ? 1 : 0) },
});
```

## Provider Configuration

### Ollama (Local)

```yaml
llm:
  providers:
    ollama:
      baseUrl: http://localhost:11434 # default; https://ollama.com when only apiKey is set
      apiKey: xxx # optional, Ollama Cloud or an authenticated server
```

### OpenAI

```yaml
llm:
  providers:
    openai:
      apiKey: sk-xxx
      baseUrl: https://api.openai.com/v1 # optional, for proxies
      api: responses # optional: responses | chat-completions
```

The official endpoint uses the OpenAI Responses API by default; a `baseUrl` pointing at another OpenAI-compatible server uses Chat Completions. Set `api` to force either wire API. Requests with stop sequences always go through Chat Completions.

### Anthropic

```yaml
llm:
  providers:
    anthropic:
      apiKey: sk-ant-xxx
```

### Google Gemini

```yaml
llm:
  providers:
    google:
      apiKey: xxx
```

### Azure OpenAI

```yaml
llm:
  providers:
    azure:
      apiKey: xxx
      endpoint: https://your-resource.openai.azure.com
      apiVersion: '2024-02-15-preview' # optional
      deployment: gpt-6.1-sol # optional
```

### AWS Bedrock

```yaml
llm:
  providers:
    bedrock:
      region: us-east-1 # optional, can use AWS config chain
      accessKeyId: xxx # optional, uses AWS credentials chain
      secretAccessKey: xxx # optional
```

Bedrock needs the optional `@aws-sdk/client-bedrock-runtime` package.

### OpenAI-Compatible Providers

```yaml
llm:
  providers:
    mistral:
      apiKey: xxx
    groq:
      apiKey: xxx
    together:
      apiKey: xxx
    deepseek:
      apiKey: xxx
    vllm:
      baseUrl: http://localhost:8000/v1
```

### Retries

Failed LLM calls (rate limits, 5xx, timeouts, dropped connections) are retried with exponential backoff and the provider's `Retry-After`. The defaults are below; `retry: false` turns retries off. See [LLM Backends](/docs/core/llm-backends#retries) for how streams and cancellation are handled.

```yaml
llm:
  retry:
    maxRetries: 2 # attempts after the first one
    baseDelay: 1000 # ms before the first retry, doubled each time
    maxDelay: 30000 # backoff cap, ms
    maxRetryAfter: 60000 # a longer Retry-After fails the call at once
```

### Prompt Caching

Agent runs cache their prompt by default (see [LLM Backends](/docs/core/llm-backends#prompt-caching)):

```yaml
llm:
  promptCache:
    ttl: 1h # Anthropic keeps the cache for an hour instead of 5 minutes
  # promptCache: false   — turn it off
```

### PII Masking

Keep emails, phones, card numbers and other personal data away from the provider (see [Security](/docs/advanced/security#pii-masking)):

```yaml
security:
  pii:
    mode: mask # mask | redact | block
    detect: [email, phone, credit_card, iban, ssn, ip_address, api_key]
    custom:
      - type: customer_id
        pattern: 'CUS-\d{6}'
```

### Prompt Injection

Detection settings live under `security.promptInjection` (see [Security](/docs/advanced/security#prompt-injection-detection)). `patterns` are regular expression sources, compiled without flags, so they are case-sensitive; pass `RegExp` objects in code for flags:

```yaml
security:
  promptInjection:
    action: block # block | warn | log
    threshold: 0.7
    patterns:
      - '[Rr]eveal (the|your) system prompt'
    failMode: secure # secure (default): a throwing classifier fails the check; open: the input passes
    allowlist:
      - ignore the previous search
```

### Sandbox

Settings for [sandboxed tools](/docs/deployment/sandbox):

```yaml
sandbox:
  allowNativeFallback: false # default true: Docker tools run on the host when Docker is unavailable
  defaults:
    image: cogitator/sandbox:base
    env:
      PYTHONUNBUFFERED: '1'
    mounts:
      - source: /srv/data
        target: /workspace/data
        readOnly: true
  pool:
    maxSize: 5
    reuseContainers: false # default; true reuses containers, so one execution's files reach the next
```

`sandbox.defaults` also takes `wasmModule`, `wasmFunction` and `wasi` for WASM tools. A custom constitution for [guardrails](/docs/advanced/constitutional-ai#custom-constitutions) can be written in YAML too, under `guardrails.constitution` (`id`, `name`, `version`, `customizable`, `strictMode` and `principles`).

## Config Merging

Configs are deep-merged with this priority (highest wins):

1. Programmatic overrides passed to `loadConfig({ overrides })`
2. Environment variables (`COGITATOR_*` and standard provider vars)
3. YAML config file values
4. Built-in defaults

Objects merge key by key; arrays and scalar values replace each other.
