# @mono-agent/config

Typed, adapter-neutral configuration for mono-agent hosts. Most users author
`mono-agent.config.json` and let `@mono-agent/agent-app` load it; install this
package directly when building a custom host, config editor, or validator.

## Category

<!-- package-metadata:start -->
<!-- Generated by scripts/generate-package-docs.mjs. Do not edit by hand. -->

Category: `core`
Tier: `core`
Catalog responsibility: Loads adapter-neutral runtime, context, memory, tool, and artifact settings.

<!-- package-metadata:end -->

## Responsibility

Load, validate, source-annotate, and redact the core runtime, context, memory,
tool/MCP, artifact, traceability, observability, provider, and sandbox settings.
Channel packages remain responsible for their own configuration.

## Install / Usage

```bash
npm install @mono-agent/config
```

```ts
import {
  buildMonoAgentConfigView,
  loadMonoAgentConfigWithSources,
} from "@mono-agent/config";

const config = await loadMonoAgentConfigWithSources({
  env: process.env,
  cwd: process.cwd(),
  jsonPath: "./mono-agent.config.json",
});

console.log(config.runtime.model);
```

A non-empty environment value overrides its mapped JSON field. Blank values are
normally ignored; the legacy `MONO_AGENT_FALLBACK_MODELS=""` clear operation is
the deliberate exception. Not every nested JSON field has an environment
counterpart, so use the [environment variable
map](https://mono-agent-docs.vercel.app/config/env-vars/) rather than assuming a
blanket override. A missing or empty JSON file contributes an empty layer.

### Agent identity and runtime routes

`agent.name` is public display metadata. It can seed human-facing trace and A2A
labels, but it never changes paths, service ids, session keys, or provider
identity. `MONO_AGENT_NAME` overrides the JSON value.

Use `runtime.fallbacks` for fallback chains. It is an ordered, uncapped array of
`{ model, effort? }` entries; omitted route effort means the provider default.
The legacy `runtime.fallbackModels` / `MONO_AGENT_FALLBACK_MODELS` surfaces were
retired in 0.21.0 and now fail at load with the replacement named; convert them
by hand.

```json
{
  "agent": { "name": "Research Companion" },
  "runtime": {
    "model": "openai-codex:gpt-5.6-terra",
    "effort": "high",
    "fallbacks": [
      { "model": "anthropic:claude-sonnet-5", "effort": "xhigh" },
      { "model": "ollama:gemma4:31b" }
    ]
  }
}
```

Every route is Pi-native, so one contract covers the whole chain. Effort values
are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, and `ultra`,
subject to the selected model's supported subset — narrowed for display, still
accepted at turn time.

`ultra` is route-specific. Reasoning-capable `pi:*` maps `ultra` to LOW; Pi
without reasoning uses OFF. Direct `codex:*` forwards `ultra` unchanged.
Mono-agent rejects `ultra` on its Claude SDK route because the pinned SDK public
contract ends at `max` (the SDK JavaScript itself forwards the value). The
Claude CLI route passes `--effort ultra`, but both tested Claude Code binaries
(SDK-bundled 2.1.206 and local 2.1.210) warn that it is unknown, ignore it, and
use default effort. Direct OpenCode rejects explicit effort. `effortRank`
places `ultra` above `max` only so keyword escalation cannot downgrade an
explicitly configured value.

### Pi credentials

Built-in Pi OAuth and API-key providers read credentials through the configured
Pi auth file. The default path is
`~/.pi/agent/auth.json`; override it with JSON or env:

```json
{
  "providers": {
    "piAuthPath": ".worklab/auth.json"
  }
}
```

```bash
MONO_AGENT_PI_AUTH_PATH=/Users/example/.pi/agent/auth.json
```

Only the path is stored in config. Token contents stay in the auth JSON file and
are never included in `redactMonoAgentConfig()`.

Pi-native transport selection is optional and defaults to `auto`. Configure
`providers.piNative.transport` or `MONO_AGENT_PI_TRANSPORT` with `auto`, `sse`,
`websocket`, or `websocket-cached`; unsupported providers ignore the choice.

### Local providers

Core config can also define local Pi providers under `providers.local`. The primary supported path is Ollama:

```json
{
  "runtime": {
    "model": "ollama:qwen3:8b",
    "workspace": "."
  },
  "providers": {
    "local": [
      {
        "id": "ollama",
        "type": "ollama",
        "baseUrl": "http://localhost:11434",
        "enabled": true,
        "models": [
          { "name": "qwen3:8b", "capabilities": { "context_window": 32768 } }
        ]
      }
    ]
  }
}
```

`runtime.maxTurns` is optional. Omit it or set `0` for unlimited runs; set `1`-`100` to keep a hard cap.

Environment overrides for the common one-provider case:

```bash
MONO_AGENT_LOCAL_PROVIDER_ID=ollama
MONO_AGENT_LOCAL_PROVIDER_TYPE=ollama
MONO_AGENT_LOCAL_PROVIDER_BASE_URL=http://localhost:11434
MONO_AGENT_LOCAL_PROVIDER_ENABLED=true
MONO_AGENT_LOCAL_PROVIDER_TRUST_PUBLIC_URL=false
```

`MONO_AGENT_LOCAL_PROVIDERS_JSON` can hold the full local-provider array. Env values win over JSON; empty env values are ignored. `MONO_AGENT_LOCAL_PROVIDER_API_KEY` and provider `apiKeyEnv` are passed only to the runtime path and are redacted from `redactMonoAgentConfig()`.

### Additional file-tool roots

For trusted host-process agents that keep `sandbox.mode` off, add narrow roots
for the managed filesystem tools without changing `runtime.workspace`:

```json
{
  "tools": {
    "filesystem": {
      "readableRoots": ["/srv/framework", "/srv/owned-worktrees"],
      "writableRoots": ["/srv/owned-worktrees"]
    }
  }
}
```

`Read`, `Glob`, and `Grep` accept readable or writable roots; `Write` and `Edit`
require a writable root. These are file-tool capabilities rather than a process
sandbox, so they do not constrain shell command contents. Additional roots use
lexical and realpath containment to reject symlink escapes. A native sandbox's
own roots remain authoritative when enabled.

### Local-first web tools

The resolved `tools.web` block configures the managed Pi `WebSearch` and
`WebFetch` tools:

```json
{
  "tools": {
    "web": {
      "search": {
        "backend": "auto",
        "maxRequestsPerRun": 4,
        "searxng": { "endpoint": "http://127.0.0.1:8088" },
        "ollama": { "baseUrl": "http://127.0.0.1:11434" },
        "codex": { "model": "gpt-5.6-luna" }
      },
      "fetch": {
        "render": "never",
        "browserCommand": "agent-browser"
      }
    }
  }
}
```

Search backend values are `auto`, strict `searxng`, strict `ollama`, strict
`codex`, and `keyless`. Auto tries explicitly configured Ollama, configured
local SearXNG, ChatGPT-subscription Codex search, then the keyless chain. Without
an Ollama block, existing SearXNG/Codex/keyless behavior is unchanged. SearXNG
endpoints are deliberately limited to unauthenticated loopback HTTP URLs. The
legacy `search.endpoint` spelling remains a compatibility alias for
`search.searxng.endpoint`. Ollama defaults to the loopback host; the exact
official `https://ollama.com` origin requires an API key named by `apiKeyEnv`,
while other public origins require HTTPS plus `trustPublicUrl` and cannot
receive that credential.
Fetch rendering config is `never` by default (browser capability disabled) or
`auto` to authorize isolated `agent-browser` use. With config `auto`, an
individual WebFetch call may use `render: "always"` for a strict browser-first
request.

Environment overrides are
`MONO_AGENT_WEB_SEARCH_BACKEND`,
`MONO_AGENT_WEB_SEARCH_MAX_REQUESTS_PER_RUN`,
`MONO_AGENT_WEB_SEARCH_SEARXNG_ENDPOINT` (with
`MONO_AGENT_WEB_SEARCH_ENDPOINT` retained as an alias),
`MONO_AGENT_WEB_SEARCH_OLLAMA_BASE_URL`,
`MONO_AGENT_WEB_SEARCH_OLLAMA_API_KEY_ENV`,
`MONO_AGENT_WEB_SEARCH_OLLAMA_TRUST_PUBLIC_URL`,
`MONO_AGENT_WEB_SEARCH_CODEX_MODEL`,
`MONO_AGENT_WEB_FETCH_RENDER`, and
`MONO_AGENT_WEB_BROWSER_COMMAND`.

### Managed memory embeddings

Journal and BuJo accept `ollama`, `lmstudio`, or `openai` in
`memory.embeddings.provider`. Keep the provider, service root, exact model, and
actual dimension explicit because they form the managed index identity:

```json
{
  "memory": {
    "mode": "journal",
    "path": "./.mono-agent/memory",
    "embeddings": {
      "provider": "lmstudio",
      "endpoint": "http://localhost:1234",
      "model": "text-embedding-nomic-embed-text-v1.5",
      "dim": 768,
      "apiKeyEnv": "LM_STUDIO_API_KEY"
    }
  }
}
```

Omit `apiKeyEnv` for keyless LM Studio. When the field is present, the loader
preserves the variable name and resolves its value only from the environment;
the app reports a missing/empty declared variable as `waiting` and never silently
retries keyless. OpenAI still requires a resolved key. Provider selection is
exclusive and does not define fallback behavior. Changing provider, model, or
dimension on an existing Journal/BuJo root requires the config-aware stopped
`mono-agent memory rebuild` workflow.

### Provider sessions and conversation history

Continuous provider sessions are configured under `runtime.session` (JSON: `{ "runtime": { "session": { "mode": "continuous", "idleTimeoutMs": 1800000 } } }`):

```bash
MONO_AGENT_SESSION_MODE=continuous           # or per-message (fresh turn with history replay)
MONO_AGENT_SESSION_IDLE_TIMEOUT_MS=1800000   # 30 min default; min 1s, max 24h
MONO_AGENT_SESSION_ROLLOVER=daily            # none (default) or daily
MONO_AGENT_SESSION_ROLLOVER_TIMEZONE=UTC     # optional IANA timezone for daily rollover
MONO_AGENT_SESSION_ROLLOVER_NOTICE=true      # opt in to adapter-visible new-bucket notices
```

In `continuous` mode (the default), consecutive messages in a conversation can
reuse one provider session (a Codex app-server thread, Claude resume token, or
Pi session transcript). Only a confirmed warm resume omits prior messages from
the next provider input; the default app host still appends every successful
turn to canonical durable conversation history. Expiry, invalidation, a
non-resumable runtime, or any configured fallback route uses the cold
history-replay path. `per-message` always starts a fresh provider turn and
relies on that canonical history for context.

### Sandbox policy

Sandbox config is optional. When any `MONO_AGENT_SANDBOX_*` variable is present, config builds a fail-closed `@mono-agent/runtime-adapter` policy rooted at `runtime.workspace`:

```bash
MONO_AGENT_SANDBOX_MODE=native
MONO_AGENT_SANDBOX_NETWORK=none
MONO_AGENT_SANDBOX_FALLBACK=fail-closed
```

Enforced network modes are `none`, `localhost`, and `allowlist`. `allowlist` reads comma-separated domains from `MONO_AGENT_SANDBOX_NETWORK_ALLOWLIST`. `all`, bare `*`, and IPv6 literals are rejected because pinned SRT 0.0.64 cannot enforce them exactly. Migrate an existing native `network.mode: "all"` config to `none`, `localhost`, or an explicit allowlist; if unrestricted shell networking is intentional, set `sandbox.mode: "off"` and remove the network policy. Unsafe host-process fallback requires both `MONO_AGENT_SANDBOX_FALLBACK=unsafe-host-process` and `MONO_AGENT_SANDBOX_UNSAFE_ALLOW_HOST_PROCESS=true`.

### Optional Anthropic cache retention

`providers.piNative.cacheRetention` accepts `"short"` or `"long"`; its environment
variable is `MONO_AGENT_PI_CACHE_RETENTION`. Nonempty MONO_AGENT environment wins
over JSON, then unset. Either explicit resolved value overrides Pi's separate
ambient `PI_CACHE_RETENTION`; unset forwards nothing and preserves Pi behavior.
The opt-in is default-off only when no external `PI_CACHE_RETENTION=long` is set.
The runtime forwards retention only to Anthropic Messages, including child
routes. Pi's `supportsLongCacheRetention` model check remains authoritative;
unsupported models receive no one-hour TTL.

One-hour writes cost **2× normal input**, reads **0.1×**, versus **1.25×** for
short-cache writes. Model support is required, and no cache hit is guaranteed.
Metadata-only diagnostics record the requested setting and observed cache TTL;
an ephemeral Anthropic cache control without an explicit TTL denotes five
minutes. Evaluate the measurement gates before separately authorizing spending.

## Architecture

### Data flow

Configuration follows one deterministic pipeline:

1. `readMonoAgentConfigJson()` reads the optional file and treats a missing or
   empty file as an empty object.
2. `loadMonoAgentConfigWithSources()` validates JSON-only structures, then
   `layerJsonOntoEnv()` maps supported JSON leaves onto the loader's env-shaped
   input without replacing non-empty environment values.
3. `loadMonoAgentConfig()` applies defaults, coercion, range checks, model
   parsing, execution-mode compatibility, and fail-closed sandbox policy.
4. Consumers use `buildMonoAgentConfigView()` for source-aware display and
   `redactMonoAgentConfig()` before logging or exposing the resolved result.

### Package structure

| Module | Purpose |
| --- | --- |
| `json-source.ts` | Typed JSON file shape plus safe read/write operations. |
| `layered-loader.ts` | JSON-to-env mapping, precedence, and JSON-source diagnostics. |
| `config.ts` | Authoritative parsing, defaults, validation, and redaction. |
| `types.ts`, `enums.ts` | Resolved config contracts and closed value sets. |
| `config-view.ts` | Source-annotated, secret-safe settings UI model. |
| `effort-keywords.ts` | Shared effort escalation vocabulary and ordering. |

## Public API

### Start here

| Need | Primary API |
| --- | --- |
| Load JSON plus environment settings | `loadMonoAgentConfigWithSources` |
| Load an already prepared environment map | `loadMonoAgentConfig` |
| Read or write the JSON source | `readMonoAgentConfigJson`, `writeMonoAgentConfigJson` |
| Display config provenance safely | `buildMonoAgentConfigView` |
| Remove secret values before output | `redactMonoAgentConfig` |
| Validate or present closed choices | `EFFORT_LEVELS`, `MEMORY_MODES`, `PERMISSION_MODES`, `ROUTE_SAFETY_MODES` |
| Apply effort-keyword escalation | `detectEffortKeyword`, `maxEffortLevel`, `effortRank` |

<!-- public-api-inventory:start -->
<!-- Generated by scripts/generate-public-api-docs.mjs. Do not edit by hand. -->

Every symbol exported by each public code entrypoint is listed below.

**`@mono-agent/config`**

```text
ALLOW_ALL_TOOLS
ArtifactRetentionConfig
BuildMonoAgentConfigViewInput
CONFIG_ENV_KEYS
ConfigViewField
ConfigViewFieldId
ConfigViewFieldSource
ConfigViewSection
ConfigViewSectionStatus
EFFORT_KEYWORD_TRIGGERS
EFFORT_LEVELS
EffortKeywordMatch
EffortKeywordTrigger
EffortLevel
LoadMonoAgentConfigInput
LoadMonoAgentConfigWithSourcesInput
MAX_AGENT_NAME_LENGTH
MEMORY_BACKENDS
MEMORY_EMBEDDINGS_PROVIDERS
MEMORY_LLM_PROVIDERS
MEMORY_MODES
MEMORY_WRITE_MODES
MemoryAgentHostLlmConfig
MemoryBackend
MemoryConsolidationConfig
MemoryEmbeddingsCircuitBreakerConfig
MemoryEmbeddingsConfig
MemoryEmbeddingsProvider
MemoryLlmConfig
MemoryLlmProvider
MemoryMode
MemoryOllamaLlmConfig
MemorySupermemoryConfig
MemoryWriteMode
MonoAgentArtifactRetentionJson
MonoAgentConfig
MonoAgentConfigError
MonoAgentConfigErrorCode
MonoAgentConfigErrorDetails
MonoAgentConfigJson
MonoAgentLocalProviderJson
MonoAgentLocalProviderModelJson
MonoAgentMemoryConsolidationJson
MonoAgentMemoryEmbeddingsCircuitBreakerJson
MonoAgentMemoryEmbeddingsJson
MonoAgentMemoryLlmJson
MonoAgentObservabilityExporterJson
MonoAgentProviderJson
MonoAgentProvidersJson
MonoAgentRuntimeFallbackJson
ObservabilityExporterConfig
PERMISSION_MODES
PermissionMode
PhoenixExporterConfig
PiNativeProviderConfig
ProviderCoverageRoute
ProviderDefinition
RETIRED_CONFIG_FIELDS
ReadMonoAgentConfigJsonResult
RedactedLocalProviderDefinition
RedactedMemoryConfig
RedactedMemoryEmbeddingsConfig
RedactedMemorySupermemoryConfig
RedactedMonoAgentConfig
RedactedObservabilityConfig
RedactedObservabilityExporterConfig
RedactedPhoenixExporterConfig
RedactedProviderDefinition
RemovedConfigWarningsInput
ResolvedProviders
RuntimeFallbackConfig
SessionMode
assertConfiguredProviderCoverage
buildMonoAgentConfigView
detectEffortKeyword
effortRank
findJsonSecretConfigWarnings
findRemovedConfigWarnings
loadMonoAgentConfig
loadMonoAgentConfigWithSources
maxEffortLevel
readMonoAgentConfigJson
redactMonoAgentConfig
resolveConfiguredProviders
resolveSupermemoryContainer
writeMonoAgentConfigJson
```

<!-- public-api-inventory:end -->

## Dependency Boundary

`@mono-agent/config` may depend on `@mono-agent/agent-contracts` and `@mono-agent/runtime-adapter`. It must not depend on communication adapters, agent harness, or UI packages.

## What This Package Does Not Own

It does not load Telegram, WhatsApp, Slack, or other adapter-specific credentials or allowlists. Adapter packages own those settings and their safety rules.

Opt in to metadata-only prompt-cache request fingerprints with
`providers.piNative.promptCacheDiagnostics` (default `false`) or
`MONO_AGENT_PI_PROMPT_CACHE_DIAGNOSTICS`. The offline reader is documented in
[Prompt-cache measurement](https://mono-agent-docs.vercel.app/runtime/prompt-cache-measurement/).

## Related Documentation

- [Configuration overview](https://mono-agent-docs.vercel.app/config/)
- [Complete configuration blueprint](https://mono-agent-docs.vercel.app/config/blueprint/)
- [Environment variable map](https://mono-agent-docs.vercel.app/config/env-vars/)
- [Generated field reference](https://mono-agent-docs.vercel.app/config/reference/)
- [Local-first web research](https://mono-agent-docs.vercel.app/tools/web-research/)
- [Runtime and provider configuration](https://mono-agent-docs.vercel.app/runtime/)
- [Package source and generated API inventory](https://github.com/robertsreberski/mono-agent/tree/main/packages/config)

## Verification

```bash
pnpm --filter @mono-agent/config run build
pnpm --filter @mono-agent/config run typecheck
pnpm --filter @mono-agent/config run test
```
