# Configuration

Anteroom is configured through layered sources: YAML config files, environment variables, CLI flags, optional team config with enforcement, and per-project config with auto-discovery.

## How Configuration Works

When Anteroom starts, it builds its configuration by merging multiple sources. Each layer can override the previous one, except for team-enforced fields which lock settings regardless of other sources.

Use `aroom config tree` (or `aroom config show`) to inspect the same hierarchy from your current working directory. Add `--keys` to show dot-path keys per layer, or `--json` for structured output. The tree consumes the config explanation and setting registry services, so it shows the same effective setting sources as `aroom config explain`, plus key setting values, required-key configured/missing status, instruction/rule paths with token estimates, skills grouped by source, MCP server status, built-in/MCP tool counts, and tool approval/filter settings.

```
┌─────────────────────────────────────────────────────┐
│  Enforced team config fields (cannot be overridden) │  ← Highest
├─────────────────────────────────────────────────────┤
│  CLI flags (--port, --approval-mode, etc.)          │
├─────────────────────────────────────────────────────┤
│  Environment variables (AI_CHAT_*)                  │
├─────────────────────────────────────────────────────┤
│  Project config (.anteroom/config.yaml in project)  │
├─────────────────────────────────────────────────────┤
│  Space config (active space config section)         │
├─────────────────────────────────────────────────────┤
│  Personal config file (~/.anteroom/config.yaml)     │
├─────────────────────────────────────────────────────┤
│  Pack config overlays                               │
├─────────────────────────────────────────────────────┤
│  Team config file (.anteroom/team.yaml)             │
├─────────────────────────────────────────────────────┤
│  Built-in defaults                                  │  ← Lowest
└─────────────────────────────────────────────────────┘
```

### Processing Order

Here is exactly what happens when you run `aroom` or `aroom chat`:

1. **Load personal config** --- Read `~/.anteroom/config.yaml` (or `~/.parlor/config.yaml` for backward compatibility) into a raw YAML dict.

2. **Validate config** --- Run the config validator to check for unknown keys, type mismatches, range violations, and structural problems. Warnings are logged but non-blocking. Errors prevent loading.

3. **Discover team config** --- Search for a team config file using this priority:
    1. `--team-config /path` CLI flag
    2. `AI_CHAT_TEAM_CONFIG` environment variable
    3. `team_config_path` field in personal config
    4. Walk up from cwd looking for `.anteroom/team.yaml`, `.claude/team.yaml`, or `anteroom.team.yaml`

4. **Trust-verify team config** --- If a team config is found, verify its SHA-256 hash against the trust store (`~/.anteroom/trusted_folders.json`). In interactive mode (CLI), prompt the user to trust new or changed files. In non-interactive mode (web UI), silently skip untrusted files.

5. **Merge team + packs + personal** --- Deep-merge team config, pack overlays, and personal config. Pack overlays sit above team defaults, while personal config can override pack values.

6. **Apply team enforcement** --- For each dot-path in the team config's `enforce` list, re-apply the team value, overriding whatever the personal config set.

7. **Merge active space config** --- If an active space supplies a `config` section, merge it above personal config and re-apply team enforcement.

8. **Discover project config** --- Walk up from the current working directory looking for `.anteroom/config.yaml`, `.claude/config.yaml`, or `.parlor/config.yaml`. Trust-verify the file via SHA-256 hash. The personal config at `~/.anteroom/config.yaml` is not treated as a project config.

9. **Merge project config** --- Overlay the project config on top of the merged result. Re-apply team enforcement so project config cannot override locked fields.

10. **Apply environment variables** --- `AI_CHAT_*` environment variables override the merged result. For example, `AI_CHAT_MODEL=gpt-4o` overrides the `ai.model` field.

11. **Build config object** --- Convert the raw dict into typed dataclass objects (`AIConfig`, `AppSettings`, `SafetyConfig`, `ReferencesConfig`, etc.) with validation and clamping.

12. **Apply CLI flag overrides** --- Flags like `--port`, `--approval-mode`, `--allowed-tools` are applied last. If a flag targets an enforced field, the override is rejected with a warning.

13. **Return** --- The final `AppConfig` object and the list of `enforced_fields` are returned. Both the web UI and CLI use this same config object.

## Quick Reference

## Explain Effective Settings

Use `aroom config explain <dot.path>` when you need to know what value is actually active and why:

```bash
aroom config explain ai.model
aroom config explain safety.approval_mode --format json
```

The explanation shows the effective value, winning layer, lower-precedence configured values, team enforcement, and pack overlay contributions when packs set the field. Sensitive values such as API keys are always redacted in text and JSON output.

In the interactive CLI, use the same path:

```text
/config explain ai.model
```

Use `aroom config sources` to inspect the active source layers and pack contributions without focusing on one setting:

```bash
aroom config sources
aroom config sources --format json
```

The web API exposes the same engine through `GET /api/config/explain/{dot_path}` and `GET /api/config/sources`, so admin UI and automation clients can consume the same provenance data without shelling out.

For changes, prefer the guided config commands (`/config set ...` or the config editor) over editing random files. Anteroom checks whether a persistent write would actually win against project, space, team, pack, and environment overrides. If a requested write would be ineffective, it should refuse or tell you the smallest scope that can work. Source-code search is a fallback for unknown settings only; routine config questions should start with config explanation and registry metadata.

## Find Relevant Settings

Use the registry-backed config help when you know the problem but not the exact key:

```bash
aroom config search approval
aroom config help safety.approval_mode
aroom config search "local embeddings" --json
```

Inside the CLI, use `/config help approval` or `/config list local embeddings`. Results include the matching field, current effective value, source layer, valid values or ranges where known, settable scopes, and a plain-language description. Sensitive settings are hidden from normal listings and redacted when explicitly included through structured APIs.

| Setting | Config Key | Env Var | Default |
|---|---|---|---|
| API endpoint | `ai.base_url` | `AI_CHAT_BASE_URL` | --- (required) |
| API key | `ai.api_key` | `AI_CHAT_API_KEY` | --- (required) |
| Model | `ai.model` | `AI_CHAT_MODEL` | `gpt-4` |
| System prompt | `ai.system_prompt` | `AI_CHAT_SYSTEM_PROMPT` | `You are a helpful assistant.` |
| SSL verification | `ai.verify_ssl` | `AI_CHAT_VERIFY_SSL` | `true` |
| Request timeout | `ai.request_timeout` | `AI_CHAT_REQUEST_TIMEOUT` | `120` |
| Host | `app.host` | --- | `127.0.0.1` |
| Port | `app.port` | `AI_CHAT_PORT` | `8080` |
| Data directory | `app.data_dir` | --- | `~/.anteroom` |
| TLS | `app.tls` | --- | `false` |
| Max upload size | `server.max_upload_mb` | `AI_CHAT_SERVER_MAX_UPLOAD_MB` | `50` |
| Built-in tools | `cli.builtin_tools` | --- | `true` |
| Max tool iterations | `cli.max_tool_iterations` | --- | `50` |
| Context warn threshold | `cli.context_warn_tokens` | `AI_CHAT_CONTEXT_WARN_TOKENS` | derived (`80000` at 128k full-request tokens) |
| Context auto-compact threshold | `cli.context_auto_compact_tokens` | `AI_CHAT_CONTEXT_AUTO_COMPACT_TOKENS` | derived (`100000` at 128k full-request tokens) |
| Context reserved output | `cli.context_reserved_output_tokens` | `AI_CHAT_CONTEXT_RESERVED_OUTPUT_TOKENS` | `ai.max_output_tokens` |
| Context warn buffer | `cli.context_warn_buffer_tokens` | `AI_CHAT_CONTEXT_WARN_BUFFER_TOKENS` | `43904` |
| Context auto-compact buffer | `cli.context_auto_compact_buffer_tokens` | `AI_CHAT_CONTEXT_AUTO_COMPACT_BUFFER_TOKENS` | `23904` |
| Live markdown streaming | `cli.streaming.enabled` | `AI_CHAT_CLI_STREAMING_ENABLED` | `false` |
| Streaming refresh rate | `cli.streaming.refresh_hz` | `AI_CHAT_CLI_STREAMING_REFRESH_HZ` | `20.0` |
| Streaming in exec mode | `cli.streaming.live_in_exec_mode` | `AI_CHAT_CLI_STREAMING_LIVE_IN_EXEC_MODE` | `false` |
| Streaming code-fence container | `cli.streaming.code_fence_container` | `AI_CHAT_CLI_STREAMING_CODE_FENCE_CONTAINER` | `true` |
| Compact preserve tail | `compaction.preserve_tail` | `AI_CHAT_COMPACT_PRESERVE_TAIL` | `6` |
| Microcompact enabled | `compaction.microcompact_enabled` | `AI_CHAT_MICROCOMPACT_ENABLED` | `true` |
| Historical tool collapse enabled | `compaction.historical_tool_collapse_enabled` | `AI_CHAT_HISTORICAL_TOOL_COLLAPSE_ENABLED` | `true` |
| Historical tool collapse trigger | `compaction.historical_tool_collapse_trigger_token_count` | `AI_CHAT_HISTORICAL_TOOL_COLLAPSE_TRIGGER_TOKEN_COUNT` | `80000` |
| Historical tool collapse recent groups | `compaction.historical_tool_collapse_keep_recent_groups` | `AI_CHAT_HISTORICAL_TOOL_COLLAPSE_KEEP_RECENT_GROUPS` | `6` |
| Historical tool collapse chars | `compaction.historical_tool_collapse_compact_chars` | `AI_CHAT_HISTORICAL_TOOL_COLLAPSE_COMPACT_CHARS` | `1000` |
| Summary trigger (messages) | `compaction.summary_trigger_msg_count` | `AI_CHAT_SUMMARY_TRIGGER_MSG_COUNT` | `80` |
| Summary trigger (tokens) | `compaction.summary_trigger_token_count` | `AI_CHAT_SUMMARY_TRIGGER_TOKEN_COUNT` | derived (`90000` at 128k) |
| Summary trigger buffer | `compaction.summary_trigger_buffer_tokens` | `AI_CHAT_SUMMARY_TRIGGER_BUFFER_TOKENS` | `33904` |
| Reactive retry cap | `compaction.reactive_max_attempts` | `AI_CHAT_REACTIVE_MAX_ATTEMPTS` | `4` |
| Summary output cap | `compaction.summary_max_completion_tokens` | `AI_CHAT_COMPACT_SUMMARY_MAX_TOKENS` | `1000` |
| Summary retry attempts | `compaction.summary_retry_max_attempts` | `AI_CHAT_COMPACT_SUMMARY_RETRY_MAX_ATTEMPTS` | `3` |
| Summary retry drop groups | `compaction.summary_retry_drop_groups` | `AI_CHAT_COMPACT_SUMMARY_RETRY_DROP_GROUPS` | `2` |
| Approval mode | `safety.approval_mode` | `AI_CHAT_SAFETY_APPROVAL_MODE` | `ask_for_writes` |
| Memory auto-approve | `memory.promotion.local_auto_approve` | `AI_CHAT_MEMORY_PROMOTION_LOCAL_AUTO_APPROVE` | `false` built-in; solo local setup may write `true` |
| Memory candidate cap | `memory.promotion.max_candidates_per_conversation` | `AI_CHAT_MEMORY_PROMOTION_MAX_CANDIDATES_PER_CONVERSATION` | `10` |
| Memory retention enabled | `memory.retention.enabled` | `AI_CHAT_MEMORY_RETENTION_ENABLED` | `false` |
| Memory retention max age | `memory.retention.max_age_days` | `AI_CHAT_MEMORY_RETENTION_MAX_AGE_DAYS` | `null` (off) |
| Memory retention idle cap | `memory.retention.idle_days` | `AI_CHAT_MEMORY_RETENTION_IDLE_DAYS` | `null` (off) |
| Memory pin respected | `memory.retention.respect_pins` | `AI_CHAT_MEMORY_RETENTION_RESPECT_PINS` | `true` |
| Memory auto-propose enabled | `memory.auto_propose.enabled` | `AI_CHAT_MEMORY_AUTO_PROPOSE_ENABLED` | `false` |
| Memory auto-propose per-turn cap | `memory.auto_propose.max_candidates_per_turn` | `AI_CHAT_MEMORY_AUTO_PROPOSE_MAX_CANDIDATES_PER_TURN` | `1` |
| Memory auto-propose categories | `memory.auto_propose.categories` | `AI_CHAT_MEMORY_AUTO_PROPOSE_CATEGORIES` | `preference,project_fact,decision,workflow_hint` |
| Memory auto-propose confidence | `memory.auto_propose.min_confidence` | `AI_CHAT_MEMORY_AUTO_PROPOSE_MIN_CONFIDENCE` | `0.8` |
| Memory auto-propose inline notice | `memory.auto_propose.notify_inline` | `AI_CHAT_MEMORY_AUTO_PROPOSE_NOTIFY_INLINE` | `true` |
| Memory auto-propose cooldown | `memory.auto_propose.cooldown_turns` | `AI_CHAT_MEMORY_AUTO_PROPOSE_COOLDOWN_TURNS` | `5` |
| CLI hierarchy — timestamps | `cli.hierarchy.show_timestamps` | `AI_CHAT_CLI_HIERARCHY_SHOW_TIMESTAMPS` | `false` |
| CLI hierarchy — turn separator char | `cli.hierarchy.turn_separator_char` | `AI_CHAT_CLI_HIERARCHY_TURN_SEPARATOR_CHAR` | `─` |
| CLI hierarchy — code-block language label | `cli.hierarchy.code_block_language_label` | `AI_CHAT_CLI_HIERARCHY_CODE_BLOCK_LANGUAGE_LABEL` | `true` |
| CLI live tools — args in verbose | `cli.live_tools.show_args_in_verbose` | `AI_CHAT_CLI_LIVE_TOOLS_SHOW_ARGS_IN_VERBOSE` | `true` |
| CLI live tools — metric suffix | `cli.live_tools.show_metric_suffix` | `AI_CHAT_CLI_LIVE_TOOLS_SHOW_METRIC_SUFFIX` | `true` |
| CLI live tools — metric max chars | `cli.live_tools.metric_max_chars` | `AI_CHAT_CLI_LIVE_TOOLS_METRIC_MAX_CHARS` | `40` |
| Tool-result density — mode | `cli.density.mode` | `AI_CHAT_CLI_DENSITY_MODE` | `normal` |
| Tool-result density — collapse repeats | `cli.density.collapse_repeats` | `AI_CHAT_CLI_DENSITY_COLLAPSE_REPEATS` | `true` |
| Tool-result density — diff context lines | `cli.density.diff_context_lines` | `AI_CHAT_CLI_DENSITY_DIFF_CONTEXT_LINES` | `3` |
| Tool-result density — head lines | `cli.density.head_lines` | `AI_CHAT_CLI_DENSITY_HEAD_LINES` | `3` |
| Tool-result density — tail lines | `cli.density.tail_lines` | `AI_CHAT_CLI_DENSITY_TAIL_LINES` | `2` |
| Team config path | --- | `AI_CHAT_TEAM_CONFIG` | --- |
| Log level | --- | `AI_CHAT_LOG_LEVEL` | `WARNING`; `DEBUG` also enables CLI per-turn diagnostics |

Proactive historical tool-result collapse can run at its token-pressure threshold before summary compaction. It is deterministic and in-memory only. Proactive summary compaction still fires when either summary trigger is reached. Token thresholds compare against the active full-request estimate, including messages, the base system prompt, dynamic extra system prompt, and tool schemas. The UI reports whether historical collapse, the token threshold, message-count threshold, summary prompt recovery, or reactive context-error recovery caused the compacting status, and the agent loop avoids repeated no-op compaction when only fixed system/tool overhead remains above the threshold.

## Config History

Anteroom automatically snapshots `config.yaml` before every managed write and at startup when external edits are detected. Use these commands to browse and restore:

```bash
aroom config history            # list all snapshots, newest first
aroom config diff <version-id>  # diff a snapshot against current config
aroom config restore <version-id>  # restore (creates a pre-restore snapshot first)
```

See [Config File — Config History](config-file.md#config-history) for full details on snapshot sources, storage location, and the durable atomic-write guarantee.

## Pages

- [Config File](config-file.md) --- full `config.yaml` YAML schema reference and config history commands
- [Environment Variables](environment-variables.md) --- all `AI_CHAT_*` env vars and precedence rules
- [Team Configuration](team-config.md) --- shared team config with enforced settings
- [Project Configuration](project-config.md) --- per-project config with auto-discovery, required keys, and shared references
- [MCP Servers](mcp-servers.md) --- MCP server setup (stdio + SSE)
- [TLS](tls.md) --- HTTPS with self-signed certificates

## Directory Equivalence

Throughout the configuration system, `.anteroom`, `.claude`, and `.agents` names are interchangeable for instruction files. `.anteroom` and `.claude` directories are interchangeable for team config discovery, skills, and rules. See [Concepts](../getting-started/concepts.md#directory-equivalence) for the full mapping.

## Related

- [Project Instructions](../cli/project-instructions.md) --- how `ANTEROOM.md` / `CLAUDE.md` / `AGENTS.md` files inject context into conversations
- [Skills](../cli/skills.md) --- reusable prompt templates (`/commit`, `/review`, custom skills)
- [Concepts](../getting-started/concepts.md) --- architecture overview and how the config feeds into the agent loop
