# Configuration reference

> config.yaml keys (schemaVersion, enabled_domains), environment variables LOOPANY_HOME, LOOPANY_SKIP_VERSION_CHECK, LOOPANY_EDITOR, and SCHEMA_VERSION guard behavior.

- Repository: superdesigndev/loopany
- GitHub: https://github.com/superdesigndev/loopany
- Human docs: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8
- Complete Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/llms-full.txt

## Source Files

- `src/core/config.ts`
- `src/core/engine.ts`
- `src/version.ts`
- `src/ui/editor.ts`
- `test/config.test.ts`
- `INSTALL_FOR_AGENTS.md`

---

---
title: "Configuration reference"
description: "config.yaml keys (schemaVersion, enabled_domains), environment variables LOOPANY_HOME, LOOPANY_SKIP_VERSION_CHECK, LOOPANY_EDITOR, and SCHEMA_VERSION guard behavior."
---

Runtime configuration for loopany splits across `$LOOPANY_HOME/config.yaml` (workspace schema version and enabled domain packs) and three environment variables that override workspace location, bypass the schema guard, or choose a GUI editor for the factory UI. Every command that loads the workspace goes through `bootstrap()` in `src/core/engine.ts`, which resolves the root, reads `config.yaml`, compares `schemaVersion` to the binary constant `SCHEMA_VERSION` from `src/version.ts`, and wires domain kind packs before artifact operations run.

## Workspace location

`getWorkspaceRoot()` returns `process.env.LOOPANY_HOME` when set; otherwise it uses `~/loopany` (via `os.homedir()`). The same root is used for `loopany init`, artifact I/O, `references.jsonl`, `audit.jsonl`, and optional `search.db`.

<ParamField body="LOOPANY_HOME" type="string">
Absolute path to the loopany workspace directory. When unset, defaults to `~/loopany`. Applies to every CLI invocation in the shell session — not per-project unless you export it per command.
</ParamField>

<Steps>
<Step title="Use a custom brain directory">
```bash
export LOOPANY_HOME=/path/to/brain
loopany init
loopany doctor
```
</Step>
<Step title="One-off override">
```bash
LOOPANY_HOME=/tmp/loopany-test loopany artifact list
```
</Step>
</Steps>

<Note>
`loopany init` is idempotent: it creates missing directories and `config.yaml`, copies bundled kinds into `kinds/`, and leaves existing files untouched. Re-running init does not bump an existing `schemaVersion`.
</Note>

## config.yaml

The file lives at `$LOOPANY_HOME/config.yaml`. The `Config` class in `src/core/config.ts` loads it with YAML parsing; a missing file is treated as empty defaults. Writes go through `persist()`, which re-serializes the in-memory object (comments in the file are not preserved on update).

### Keys

| Key | Type | Default when absent | Written by |
|-----|------|---------------------|------------|
| `schemaVersion` | string (semver-style) | `0.1.0` (`ASSUMED_LEGACY_VERSION`) | `loopany init` (new file only), migration final scripts via `setSchemaVersion()`, manual edit |
| `enabled_domains` | string array | `[]` | `loopany domain enable` / `domain disable` |

<ParamField body="schemaVersion" type="string" required={false}>
Workspace on-disk format version. Compared to `SCHEMA_VERSION` in `src/version.ts` on every `bootstrap()` unless the version check is skipped. Pre-framework workspaces that never wrote this field are read as `0.1.0`.
</ParamField>

<ParamField body="enabled_domains" type="string[]" required={false}>
Names of domain packs whose kind definitions are merged at bootstrap. Each enabled name loads `domains/<name>/kinds/` in addition to workspace-global `kinds/`. Entries are stored sorted; `enableDomain` is idempotent; `disableDomain` on an unknown name is a no-op.
</ParamField>

### Example files

Fresh init (`loopany init` when `config.yaml` does not exist):

```yaml
# loopany workspace config
schemaVersion: 0.2.0
```

With domains enabled:

```yaml
schemaVersion: 0.2.0
enabled_domains:
  - ads
  - crm
```

Legacy workspace (no `schemaVersion` key):

```yaml
enabled_domains:
  - crm
```

At load time, `Config.schemaVersion()` returns `0.1.0` until a migration or `setSchemaVersion()` updates the file.

## VERSION vs SCHEMA_VERSION

The binary carries two independent version constants in `src/version.ts`:

| Constant | Current value | Meaning |
|----------|-----------------|---------|
| `VERSION` | `0.2.0` | Package / CLI semver (`loopany --version`) |
| `SCHEMA_VERSION` | `0.2.0` | Expected workspace format; bump only when on-disk layout, kinds, frontmatter, or references require migration |

`schemaVersion` in `config.yaml` tracks the workspace; `SCHEMA_VERSION` tracks what the running binary accepts. They can drift across releases until you migrate the workspace forward.

## Schema version guard

On each `bootstrap()`:

1. Resolve root via `LOOPANY_HOME` or default.
2. Fail with `WorkspaceNotFoundError` if `kinds/` is missing (run `loopany init`).
3. Load `config.yaml` and compute `config.schemaVersion()`.
4. Unless skipped, if `config.schemaVersion() !== SCHEMA_VERSION`, throw `SchemaVersionMismatchError`.

```mermaid
sequenceDiagram
  participant CLI as cli.ts
  participant Boot as bootstrap()
  participant CFG as config.yaml
  participant VER as SCHEMA_VERSION

  CLI->>Boot: dispatch command
  Boot->>CFG: Config.load(root)
  CFG-->>Boot: schemaVersion()
  alt versions equal or check skipped
    Boot-->>CLI: Engine
  else mismatch
    Boot-->>CLI: SchemaVersionMismatchError
    CLI-->>CLI: stderr + exit 1
  end
```

### Error shape

`SchemaVersionMismatchError` message (paraphrased structure):

```
Workspace schema is v<workspace> but this binary expects v<binary>.
Read `skills/migrations/v<workspace>-to-v<binary>/SKILL.md`
and run `loopany migrate v<workspace>-to-v<binary>`.
```

`cli.ts` catches this error, prints `Error: …` to stderr, and exits with code `1`. Artifact commands (`list`, `get`, `create`, etc.) do not run on a stale workspace.

### Commands that bypass the guard

| Surface | Mechanism |
|---------|-----------|
| `loopany doctor` | `bootstrap({ skipVersionCheck: true })` — reports `schema version` check fail instead of crashing |
| `loopany migrate` | Same bypass — discovery works on stale workspaces |
| Env `LOOPANY_SKIP_VERSION_CHECK=1` | `process.env.LOOPANY_SKIP_VERSION_CHECK === '1'` in `bootstrap()` |
| Migration scripts | Typically run with `LOOPANY_SKIP_VERSION_CHECK=1` while transforming data |

<Warning>
Do not leave `LOOPANY_SKIP_VERSION_CHECK=1` set in your shell profile for normal use. It disables the safety gate that prevents a newer binary from reading an unmigrated workspace (or the reverse).
</Warning>

### Recovery flow

<Steps>
<Step title="Confirm mismatch">
```bash
loopany doctor
# or
loopany migrate
```
`migrate` with no args prints workspace schema, binary expectation, and the next migration name when one exists whose `from` matches the workspace version.
</Step>
<Step title="Run migration">
```bash
loopany migrate v0.1.0-to-v0.2.0
```
Read the printed `SKILL.md` and run scripts in order (migration scripts live under `skills/migrations/` in the repo; the CLI does not auto-execute them).
</Step>
<Step title="Verify">
Final migration step should call `config.setSchemaVersion('0.2.0')` (or the target `SCHEMA_VERSION`). Then:
```bash
loopany doctor
```
Schema version check should pass when `config.yaml` matches `SCHEMA_VERSION`.
</Step>
</Steps>

## Environment variables

### LOOPANY_SKIP_VERSION_CHECK

<ParamField body="LOOPANY_SKIP_VERSION_CHECK" type="string">
When set to `1`, skips the `schemaVersion === SCHEMA_VERSION` check in `bootstrap()`. Used by migration scripts and tests; also available as `bootstrap({ skipVersionCheck: true })` for `doctor` and `migrate`.
</ParamField>

```bash
LOOPANY_SKIP_VERSION_CHECK=1 loopany artifact list
```

### LOOPANY_EDITOR

<ParamField body="LOOPANY_EDITOR" type="string">
Shell-style command string for opening artifact markdown in a GUI editor from the factory UI (`openInEditor` in `src/ui/editor.ts`). First token is the executable; remaining tokens are arguments. Supports simple quoted segments (`code --wait`, `cursor`).
</ParamField>

Resolution order:

1. `LOOPANY_EDITOR` if non-empty after trim
2. Platform default: `open` (macOS), `xdg-open` (Linux), `start` (Windows)

<Info>
`$VISUAL` and `$EDITOR` are intentionally ignored. Terminal editors spawned from the factory server would not show a window; set `LOOPANY_EDITOR` explicitly for GUI tools, or use a wrapper script for complex invocations.
</Info>

```bash
export LOOPANY_EDITOR="cursor"
loopany factory
```

## enabled_domains and bootstrap

When domains are enabled, bootstrap builds extra kind pack directories:

```
domains/<name>/kinds/   # for each name in enabled_domains
```

`KindRegistry.load()` merges workspace `kinds/` with those pack dirs. Disabling a domain removes its kinds from the registry on the next command; it does not delete artifacts tagged with that domain.

CLI persistence:

```bash
loopany domain enable crm
loopany domain disable ads
loopany domain list    # enabled + observed-only (in artifacts but not enabled)
```

`loopany doctor` emits a **warn** (not fail) when an artifact’s `domain` frontmatter is not listed in `enabled_domains` — useful for catching typos or forgotten enables.

## Operational checklist

| Goal | Action |
|------|--------|
| Point CLI at a test workspace | `LOOPANY_HOME=/tmp/ws loopany …` |
| See version skew | `loopany migrate` or `loopany doctor` |
| Fix version skew | Follow migration skill; bump `schemaVersion` to match binary |
| Enable CRM kinds pack | `loopany domain enable crm` (requires `domains/crm/kinds/` on disk) |
| Open artifacts from factory in VS Code | `export LOOPANY_EDITOR="code -n"` |

<Check>
Healthy steady state: `config.yaml` contains `schemaVersion: 0.2.0` (matching current `SCHEMA_VERSION`), `loopany doctor` exits `0`, and routine commands run without `LOOPANY_SKIP_VERSION_CHECK`.
</Check>

## Related pages

<CardGroup>
<Card title="Schema migration" href="/schema-migration">
`schemaVersion` bumps, `loopany migrate` discovery, and v0.1.0→v0.2.0 script workflow.
</Card>
<Card title="Domains" href="/domains">
Domain packs under `domains/<name>/`, when to enable, and scope-local kinds.
</Card>
<Card title="Workspace setup" href="/workspace-setup">
`loopany init`, bundled kinds, onboarding, and doctor verification.
</Card>
<Card title="Artifacts and workspace" href="/artifacts-and-workspace">
Full on-disk layout under `$LOOPANY_HOME` beyond `config.yaml`.
</Card>
<Card title="Doctor and troubleshooting" href="/doctor-and-troubleshooting">
Schema version check in doctor output and common recovery paths.
</Card>
<Card title="Installation" href="/installation">
Clone, Bun, `bun link`, and initial `LOOPANY_HOME` setup.
</Card>
</CardGroup>
