# loopany Documentation

> Reference for the loopany CLI and agent skill library: markdown artifacts, append-only reference graph, workspace schema, and harness-neutral install paths for long-horizon agent memory.

## Context Links

- [Agent index](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/llms.txt)
- [Human interactive docs](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8)
- [GitHub repository](https://github.com/superdesigndev/loopany)

## Repository Metadata

- Repository: superdesigndev/loopany

- Generated: 2026-06-05T19:05:11.667Z
- Updated: 2026-06-05T19:21:01.699Z
- Runtime: Grok CLI
- Format: Documentation
- Pages: 22

## Page Index

- 01. [Overview](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/01-overview.md) - What loopany exposes (CLI, workspace, skills), runtime assumptions (Bun, $LOOPANY_HOME), and the shortest path from init to a running mission-backed brain.
- 02. [Installation](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/02-installation.md) - Clone the repo, install Bun, link the CLI, set PATH, initialize $LOOPANY_HOME, and wire the resolver injection into Claude Code, Codex, or other agent hosts.
- 03. [Quickstart](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/03-quickstart.md) - First successful session: init workspace, create a mission and task, add a reference edge, run doctor, and optional reindex/search or factory UI.
- 04. [Artifacts and workspace](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/04-artifacts-and-workspace.md) - On-disk layout under $LOOPANY_HOME: artifacts/<dirName>/<slug>.md, config.yaml, references.jsonl, audit.jsonl, optional search.db, and v0.2 slug-as-id storage rules.
- 05. [Kinds and validation](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/05-kinds-and-validation.md) - Markdown kind definitions, dynamic Zod frontmatter schemas, status machines, indexedFields, slug rules, and immutable write semantics (append, status flip, supersede).
- 06. [Reference graph](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/06-reference-graph.md) - Append-only references.jsonl edges, implicit mentions from frontmatter, canonical relation verbs, refs/trace queries, and wiki-link [[id]] conventions.
- 07. [Domains](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/07-domains.md) - Scope-local packs under domains/<name>/, enabled_domains in config.yaml, domain-scoped kind overrides, and when to propose a domain vs a note.
- 08. [Skills library](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/08-skills-library.md) - Agent-readable SKILL.md packs (resolver, core, capture, reflect, review), routing table, cross-skill chaining, and harness-neutral memory injection.
- 09. [Self-improvement loop](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/09-self-improvement-loop.md) - Task ## Outcome evidence, learning beliefs, skill-proposal accept/reject flow, checkAt followups, and the rule that agents never edit skills directly.
- 10. [Workspace setup](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/10-workspace-setup.md) - Run loopany init, verify bundled kinds copied, complete ONBOARDING.md once, register cadence (cron vs session-boundary), and confirm doctor passes.
- 11. [Capture workflow](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/11-capture-workflow.md) - End-of-task capture quality gate, event→kind routing (task/signal/note), subagent dispatch pattern, and duplicate detection via artifact list --contains.
- 12. [Reflect workflow](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/12-reflect-workflow.md) - Gather recent outcomes and dismissed signals, pattern thresholds, write learning and skill-proposal artifacts, and accept/reject proposals with git-backed skill diffs.
- 13. [Periodic review](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/13-periodic-review.md) - Daily followups (--due today), weekly doctor + overdue sweep, monthly mission-drift checks, and closure rules for checkAt-bearing artifacts.
- 14. [Schema migration](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/14-schema-migration.md) - schemaVersion vs binary VERSION, SchemaVersionMismatchError recovery, loopany migrate discovery, and v0.1.0→v0.2.0 script-driven workspace transforms.
- 15. [CLI reference](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/15-cli-reference.md) - Full command surface from loopany --help: init, artifact/*, refs, trace, domain, followups, search, reindex, factory, kind list, doctor, migrate, and JSON stdout conventions.
- 16. [Artifact commands](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/16-artifact-commands.md) - create/get/list/append/status/set flags, per-kind --field validation, --slug and --content-file, journal auto-management, and required ## Outcome on terminal task statuses.
- 17. [Graph, search, and scheduling](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/17-graph-search-and-scheduling.md) - refs add/query, trace BFS, followups --due, hybrid search/reindex (--no-embed), factory UI (--port/--no-open), and audit.jsonl side effects.
- 18. [Configuration reference](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/18-configuration-reference.md) - config.yaml keys (schemaVersion, enabled_domains), environment variables LOOPANY_HOME, LOOPANY_SKIP_VERSION_CHECK, LOOPANY_EDITOR, and SCHEMA_VERSION guard behavior.
- 19. [Artifact lifecycle example](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/19-artifact-lifecycle-example.md) - End-to-end recipe: signal → task with led-to/addresses edges, status transitions, brief output, and journal linkage—mirroring test/scenario.e2e and skill-regression flows.
- 20. [Self-improvement example](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/20-self-improvement-example.md) - Recipe: three done tasks with outcomes → reflect writes learning + pending skill-proposal → user accepts → skill file diff and proposal Outcome recorded.
- 21. [Build and test](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/21-build-and-test.md) - bun install, typecheck, bun test unit/e2e, compiled binary via bun build --compile, and skill-regression.sh requirements (claude CLI, API key).
- 22. [Doctor and troubleshooting](https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/22-doctor-and-troubleshooting.md) - doctor checks (workspace, schema version, kinds, artifacts, references, onboarding), common failures (WorkspaceNotFound, Zod validation, missing search index), and factory/reindex recovery steps.

## Source File Index

- `CLAUDE.md`
- `injections/resolver-memory.md`
- `INSTALL_FOR_AGENTS.md`
- `ONBOARDING.md`
- `package.json`
- `README.md`
- `skills/loopany-capture/SKILL.md`
- `skills/loopany-core/conventions/relations.md`
- `skills/loopany-core/conventions/taxonomy.md`
- `skills/loopany-core/kinds/journal.md`
- `skills/loopany-core/kinds/learning.md`
- `skills/loopany-core/kinds/mission.md`
- `skills/loopany-core/kinds/note.md`
- `skills/loopany-core/kinds/person.md`
- `skills/loopany-core/kinds/signal.md`
- `skills/loopany-core/kinds/skill-proposal.md`
- `skills/loopany-core/kinds/task.md`
- `skills/loopany-core/SKILL.md`
- `skills/loopany-reflect/SKILL.md`
- `skills/loopany-resolver/SKILL.md`
- `skills/loopany-review/references/daily.md`
- `skills/loopany-review/references/monthly.md`
- `skills/loopany-review/references/weekly.md`
- `skills/loopany-review/SKILL.md`
- `skills/migrations/README.md`
- `skills/migrations/v0.1.0-to-v0.2.0/SKILL.md`
- `src/cli.ts`
- `src/commands/argv.ts`
- `src/commands/artifact-append.ts`
- `src/commands/artifact-create.ts`
- `src/commands/artifact-get.ts`
- `src/commands/artifact-list.ts`
- `src/commands/artifact-set.ts`
- `src/commands/artifact-status.ts`
- `src/commands/body-input.ts`
- `src/commands/doctor.ts`
- `src/commands/domain.ts`
- `src/commands/factory.ts`
- `src/commands/followups.ts`
- `src/commands/init.ts`
- `src/commands/migrate.ts`
- `src/commands/refs.ts`
- `src/commands/reindex.ts`
- `src/commands/search.ts`
- `src/commands/trace.ts`
- `src/core/artifact-store.ts`
- `src/core/audit.ts`
- `src/core/config.ts`
- `src/core/engine.ts`
- `src/core/kind-registry.ts`
- `src/core/link-parser.ts`
- `src/core/operations.ts`
- `src/core/references.ts`
- `src/core/search-store.ts`
- `src/core/slug.ts`
- `src/ui/editor.ts`
- `src/ui/server.ts`
- `src/version.ts`
- `test/artifact-store.test.ts`
- `test/cli.e2e.test.ts`
- `test/config.test.ts`
- `test/helpers/cli.ts`
- `test/kind-registry.test.ts`
- `test/migration-framework.test.ts`
- `test/migration-v0.1-to-v0.2.e2e.test.ts`
- `test/references.test.ts`
- `test/scenario.e2e.test.ts`
- `test/skill-regression.sh`
- `tsconfig.json`

---

## 01. Overview

> What loopany exposes (CLI, workspace, skills), runtime assumptions (Bun, $LOOPANY_HOME), and the shortest path from init to a running mission-backed brain.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/01-overview.md
- Generated: 2026-06-05T18:56:39.037Z

### Source Files

- `README.md`
- `CLAUDE.md`
- `src/cli.ts`
- `src/version.ts`
- `package.json`
- `skills/loopany-resolver/SKILL.md`

---
title: "Overview"
description: "What loopany exposes (CLI, workspace, skills), runtime assumptions (Bun, $LOOPANY_HOME), and the shortest path from init to a running mission-backed brain."
---

loopany ships as a Bun-driven TypeScript CLI (`src/cli.ts`, entry `loopany` in `package.json`) that reads and writes a single per-user workspace under `$LOOPANY_HOME` (default `~/loopany`). Judgment lives in markdown skills under `skills/`; deterministic validation, storage, graph indexing, and search run in code. The binary reports `0.2.0` for both `loopany --version` and workspace `schemaVersion` (`src/version.ts`); `bootstrap()` refuses most commands when those versions diverge.

## What loopany exposes

Three cooperating surfaces attach any agent harness (Claude Code, Codex, Hermes, custom) to one persistent brain:

| Surface | Location | Role |
|---------|----------|------|
| **CLI** | `loopany` → `src/cli.ts` | Artifact CRUD, reference graph, domains, followups, hybrid search, migrations, `doctor`, optional `factory` UI |
| **Workspace** | `$LOOPANY_HOME` | Canonical markdown artifacts, `config.yaml`, append-only `references.jsonl` and `audit.jsonl`, optional derived `search.db` |
| **Skills** | `skills/loopany-*/SKILL.md` in the cloned repo | Routing, capture, reflect, review, kind playbooks — read by the agent, not executed by the runtime |

```mermaid
flowchart TB
  subgraph harness["Agent harness"]
    Agent["Agent session"]
  end
  subgraph skills["Skills (markdown, repo)"]
    Resolver["loopany-resolver/SKILL.md"]
    Core["loopany-core + kinds/*.md"]
    Capture["loopany-capture"]
    Reflect["loopany-reflect"]
    Review["loopany-review"]
  end
  subgraph cli["CLI runtime (Bun / TypeScript)"]
    CLI["src/cli.ts"]
    Engine["bootstrap() → engine.ts"]
    Store["ArtifactStore"]
    Refs["ReferenceGraph"]
    Index["ArtifactIndex / search.db"]
  end
  subgraph disk["$LOOPANY_HOME"]
    Artifacts["artifacts/&lt;dirName&gt;/&lt;slug&gt;.md"]
    Config["config.yaml"]
    RefsFile["references.jsonl"]
    Audit["audit.jsonl"]
  end
  Agent -->|"Read SKILL.md on demand"| Resolver
  Resolver --> Core
  Resolver --> Capture
  Resolver --> Reflect
  Resolver --> Review
  Agent -->|"loopany …"| CLI
  CLI --> Engine
  Engine --> Store
  Engine --> Refs
  Engine --> Index
  Store --> Artifacts
  Refs --> RefsFile
  Engine --> Config
  CLI --> Audit
```

<Note>
Skills are portable markdown packs — not tied to a model provider. Host memory injection (for example `injections/resolver-memory.md` into `~/.claude/CLAUDE.md` or `AGENTS.md`) only points at `~/loopany-src/skills/loopany-resolver/SKILL.md`; skill content can change via `git pull` without re-running injection.
</Note>

## Runtime assumptions

<ParamField body="Bun" type="runtime" required>
  Required to run and link the CLI (`bun install`, `bun link`, `bun run src/cli.ts`). A compiled binary is optional via `bun build --compile --outfile bin/loopany src/cli.ts`.
</ParamField>

<ParamField body="LOOPANY_HOME" type="string">
  Workspace root. Defaults to `~/loopany` when unset (`getWorkspaceRoot()` in `src/core/engine.ts`).
</ParamField>

<ParamField body="LOOPANY_SKIP_VERSION_CHECK" type="string">
  When set to `1`, skips `schemaVersion` vs `SCHEMA_VERSION` guard — used by migration scripts and honored by `bootstrap({ skipVersionCheck: true })`.
</ParamField>

<ParamField body="Node 22+" type="alternative">
  `CLAUDE.md` lists Node 22+ as an alternative runtime; the shipped scripts and tests target Bun.
</ParamField>

Workspace detection is minimal: `bootstrap()` requires `$LOOPANY_HOME/kinds` to exist (created by `loopany init`). Missing workspace → `WorkspaceNotFoundError`. Stale `config.yaml#schemaVersion` → `SchemaVersionMismatchError` with a pointer to `loopany migrate` and `skills/migrations/v*-to-v*/SKILL.md`.

## Workspace layout (v0.2)

After `loopany init`, expect:

:::files
$LOOPANY_HOME/
├── config.yaml              # schemaVersion, enabled_domains
├── kinds/                   # copied from skills/loopany-core/kinds/*.md
├── artifacts/
│   ├── missions/            # mission (flat)
│   ├── people/              # person (flat)
│   ├── tasks/               # task (default dirName: tasks)
│   ├── notes/
│   ├── journal/YYYY/        # journal (slugLayout: year)
│   └── …                    # other kinds → artifacts/<dirName>/
├── references.jsonl         # append-only graph edges
├── audit.jsonl              # CLI operation log (best-effort)
└── search.db                # optional; rebuilt by loopany reindex
:::

Artifact IDs are **slugs** (no kind prefix). Pass `--slug` on create for anything you will cite with `[[slug]]`; otherwise the CLI derives from `--title` or mints `YYYYMMDD-HHMMSS-xxx` (`src/cli.ts` help text).

Bundled core kinds (copied at init): `mission`, `task`, `signal`, `person`, `note`, `learning`, `skill-proposal`, `brief`, `journal`. Kind schemas and status machines live in markdown under `kinds/`; the runtime builds Zod validators dynamically (`KindRegistry`).

## CLI command surface (summary)

Most mutating commands call `bootstrap()`, write JSON to stdout, and append an audit row when the workspace exists.

| Group | Commands |
|-------|----------|
| Init | `init` |
| Artifacts | `artifact create \| get \| list \| append \| status \| set` |
| Graph | `refs`, `refs add`, `trace` |
| Domains | `domain list \| enable \| disable` |
| Scheduling | `followups [--due today\|overdue]` |
| Search | `search`, `reindex [--force] [--no-embed]` |
| UI | `factory [--port N] [--no-open]` |
| System | `kind list`, `doctor`, `migrate`, `--version` |

<Info>
`doctor` and `migrate` call `bootstrap({ skipVersionCheck: true })` so they can report or fix version skew instead of crashing first.
</Info>

## Skills library and resolver gate

Every loopany interaction is expected to start at `skills/loopany-resolver/SKILL.md`:

```bash
loopany artifact list --kind mission --status active
```

**No active mission → run `ONBOARDING.md` and stop.** Core and capture skills repeat the same bootstrap rule.

| Skill | Triggers (examples) |
|-------|---------------------|
| `loopany-resolver` | Session start, ambiguous intent, multi-skill workflows |
| `loopany-core` | Any artifact CRUD; relation verb choice |
| `loopany-capture` | Substantive work ended (PR shipped, incident resolved) |
| `loopany-reflect` | ≥3 done tasks with outcomes; accept/reject skill-proposals |
| `loopany-review` | Daily followups, weekly doctor/sweep, monthly mission drift |

Cross-skill chains documented in the resolver include **capture → core**, **capture → reflect** (after ≥3 captures), **reflect → core** (writes `learning` / `skill-proposal`), and **review → reflect** on weekly resolution thresholds.

Agents **never edit skill files directly** — behavior changes flow through `skill-proposal` artifacts, user accept/reject, then git-backed diffs (see self-improvement docs).

## Mission-backed brain

A mission artifact (`kinds/mission.md`) is the standing intention the brain serves: long-running pursuit, `status: active`, body sections like `## Current hypothesis`. Tasks, signals, and briefs relate to it via `mentions`, `domain`, or graph edges — not by replacing the mission kind.

`loopany doctor` treats onboarding as incomplete without:

- a `self` **person** artifact, and  
- at least one **mission** with `status: active`

Until both exist, the resolver intentionally blocks routine work — the workspace has no declared purpose.

## Shortest path: init → running brain

<Steps>
<Step title="Install CLI and clone skills source">

```bash
git clone https://github.com/superdesigndev/loopany.git ~/loopany-src && cd ~/loopany-src
curl -fsSL https://bun.sh/install | bash
export PATH="$HOME/.bun/bin:$PATH"
bun install && bun link
loopany --version
```

</Step>

<Step title="Initialize workspace">

```bash
loopany init
# Optional: LOOPANY_HOME=/path/to/brain loopany init
```

Creates `$LOOPANY_HOME/kinds`, `artifacts/`, and `config.yaml` with `schemaVersion: 0.2.0`. Idempotent — existing files are preserved. If no `artifacts/missions/*.md` exists, init prints a pointer to `ONBOARDING.md`.

</Step>

<Step title="Onboard and create active mission">

Read `ONBOARDING.md` once. The agent runs a short conversation, then creates artifacts — including one `mission` with `status: active` and usually a `person` slug `self`. Do not re-run if an active mission already exists.

Example mission create (fields per `kinds/mission.md`):

```bash
loopany artifact create --kind mission \
  --slug ship-v1 \
  --title "Ship loopany v1" \
  --status active \
  --content-file -
```

</Step>

<Step title="Wire resolver into host memory (coding CLIs)">

Append `injections/resolver-memory.md` so every session ends by re-reading the resolver (Claude Code: `~/.claude/CLAUDE.md`; Codex: `~/AGENTS.md`). Details in `INSTALL_FOR_AGENTS.md` Step 5.

</Step>

<Step title="Verify">

```bash
loopany doctor
loopany artifact list --kind mission --status active
```

Exit `0` from `doctor` means workspace, kinds, artifacts, references, and onboarding checks passed. Warnings (for example tasks without `mentions` of a mission) do not fail the run.

</Step>
</Steps>

After verification, normal operation is: resolver → appropriate skill → `loopany artifact …` / `loopany refs …`. End-of-task capture and periodic review keep the graph and outcomes current; `loopany reindex` rebuilds search when needed.

## Design constraints (operational)

- **Thin harness, fat skills** — code handles I/O, validation, and graph indexing; judgment stays in SKILL.md files.  
- **Markdown + JSONL source of truth** — no database; `search.db` is disposable.  
- **Immutable cited artifacts** — append body sections, transition `status`, or supersede; do not edit agent-produced history in place.  
- **Open registries** — `kind` and `relation` extend via config/markdown, not TypeScript enums.  
- **Agent proposes, human accepts** — new kinds, domains, and skill changes go through explicit artifacts and user decisions.

Unit and E2E tests run with `bun test`; skill regression (`test/skill-regression.sh`) exercises the five skills against a fresh `LOOPANY_HOME` per scenario.

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Clone, Bun, `bun link`, PATH, `$LOOPANY_HOME`, and resolver injection into agent hosts.
</Card>
<Card title="Quickstart" href="/quickstart">
First session: mission, task, reference edge, and `doctor`.
</Card>
<Card title="Artifacts and workspace" href="/artifacts-and-workspace">
On-disk layout, slug-as-id rules, and v0.2 storage paths.
</Card>
<Card title="Skills library" href="/skills-library">
Resolver routing, skill packs, and harness-neutral memory injection.
</Card>
<Card title="Workspace setup" href="/workspace-setup">
`loopany init`, bundled kinds, onboarding, cadence, and doctor checks.
</Card>
<Card title="CLI reference" href="/cli-reference">
Full command list, flags, and JSON stdout conventions.
</Card>
</CardGroup>

---

## 02. Installation

> Clone the repo, install Bun, link the CLI, set PATH, initialize $LOOPANY_HOME, and wire the resolver injection into Claude Code, Codex, or other agent hosts.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/02-installation.md
- Generated: 2026-06-05T18:56:45.415Z

### Source Files

- `INSTALL_FOR_AGENTS.md`
- `package.json`
- `src/cli.ts`
- `injections/resolver-memory.md`
- `src/commands/init.ts`
- `ONBOARDING.md`

---
title: "Installation"
description: "Clone the repo, install Bun, link the CLI, set PATH, initialize $LOOPANY_HOME, and wire the resolver injection into Claude Code, Codex, or other agent hosts."
---

loopany ships as a Bun-linked CLI (`package.json` → `src/cli.ts`) that scaffolds a per-user workspace at `$LOOPANY_HOME` (default `~/loopany`) via `loopany init`, while the skill library and resolver injection stay in the cloned repository checkout (conventionally `~/loopany-src`).

## Prerequisites

| Requirement | Notes |
|-------------|--------|
| [Bun](https://bun.sh) | Runtime and package manager; the CLI shebang is `#!/usr/bin/env bun`. |
| Git | Clone `https://github.com/superdesigndev/loopany`. |
| Agent host (optional) | Claude Code, Codex, Hermes, OpenClaw, or any harness that can read markdown skills and append persistent memory. |

<Note>
`package.json` lists version `0.1.0`; `loopany --version` prints the binary version from `src/version.ts` (currently `0.2.0` with workspace schema `0.2.0`).
</Note>

## Repository layout after clone

Two on-disk locations matter: the **source checkout** (CLI + skills + injections) and the **workspace** (artifacts + copied kinds + config).

```text
~/loopany-src/                    # git clone (path is your choice)
├── src/cli.ts                    # bin entry: loopany
├── skills/
│   ├── loopany-resolver/SKILL.md # dispatcher — read at end of tasks
│   ├── loopany-core/kinds/*.md   # bundled kind defs copied by init
│   └── loopany-capture|reflect|review/ ...
└── injections/resolver-memory.md # host memory snippet

~/loopany/                        # $LOOPANY_HOME (default)
├── config.yaml                   # schemaVersion, enabled_domains
├── kinds/*.md                    # copied once from bundled kinds
├── artifacts/                    # markdown artifacts (created on use)
├── references.jsonl              # created on first graph edge write
└── audit.jsonl                   # created on first post-init CLI op
```

## Install the CLI

<Steps>
<Step title="Clone the repository">

```bash
git clone https://github.com/superdesigndev/loopany.git ~/loopany-src
cd ~/loopany-src
```

</Step>

<Step title="Install Bun (if needed)">

```bash
curl -fsSL https://bun.sh/install | bash
export PATH="$HOME/.bun/bin:$PATH"
```

Persist `export PATH="$HOME/.bun/bin:$PATH"` in `~/.zshrc` or `~/.bashrc`, then `source` the file.

</Step>

<Step title="Install dependencies and link globally">

```bash
bun install && bun link
```

`bun link` registers the `loopany` bin from `package.json` (`"loopany": "./src/cli.ts"`) on your PATH via Bun’s global link.

</Step>

<Step title="Verify the CLI">

```bash
loopany --version
```

<Check>
Expected output shape: `loopany 0.2.0` (semver from `src/version.ts`).
</Check>

If `loopany` is not found, Bun’s bin directory is not on `PATH` — add `$HOME/.bun/bin` and open a new shell.

</Step>
</Steps>

### Optional: compiled binary

For a standalone executable without invoking `bun run` each time:

```bash
bun run build
# writes bin/loopany via: bun build --compile --outfile bin/loopany src/cli.ts
```

Add the `bin/` directory to `PATH`, or copy `bin/loopany` somewhere already on `PATH`.

## Initialize the workspace

`loopany init` scaffolds `$LOOPANY_HOME`, copies bundled kind definitions from `skills/loopany-core/kinds/`, and writes `config.yaml` with the current `SCHEMA_VERSION`. The command is **idempotent**: existing files are left untouched; only missing paths and kind files are created.

```bash
loopany init
```

<ResponseExample>

```text
Initialized loopany workspace at /Users/you/loopany
Created N file(s).

NEXT — read ONBOARDING.md and start the onboarding conversation.
Without an active mission artifact, the brain has no reason to exist.
```

</ResponseExample>

The `NEXT` block appears when `artifacts/missions/` has no `.md` files yet (`needsOnboarding` in `runInit`).

### Custom workspace location

<ParamField body="LOOPANY_HOME" type="path">
Overrides the default `~/loopany`. Resolved in `getWorkspaceRoot()` as `process.env.LOOPANY_HOME ?? join(homedir(), 'loopany')`. Applies to every command after export — not only `init`.
</ParamField>

```bash
export LOOPANY_HOME=/path/to/brain
loopany init
```

<Warning>
Most commands call `bootstrap()`, which requires a `kinds/` directory under `$LOOPANY_HOME`. Without `loopany init`, you get `WorkspaceNotFoundError: No loopany workspace at … Run loopany init first.`
</Warning>

### What `init` creates

| Path | Created by init |
|------|-----------------|
| `$LOOPANY_HOME/` | Yes (if missing) |
| `kinds/` | Yes; copies `task`, `signal`, `person`, `mission`, `note`, `learning`, `skill-proposal`, `brief`, `journal` from bundled kinds |
| `artifacts/` | Empty directory |
| `config.yaml` | Yes, with `schemaVersion: <SCHEMA_VERSION>` |
| `references.jsonl` | No — first `refs add` or append creates the file |
| `domains/` | No — created when domains are enabled later |

## Agent-driven install (harness paste)

Hosts can fetch the canonical agent install doc without shell steps from the user:

```text
Retrieve and follow the instructions at:
https://raw.githubusercontent.com/superdesigndev/loopany/main/INSTALL_FOR_AGENTS.md
```

That document covers clone → link → `init` → onboarding → cadence choice → resolver injection → `loopany doctor`, including tone rules (keep CLI vocabulary out of user-facing chat).

## Wire resolver injection into agent hosts

loopany does not load skills at runtime. Agents **read** markdown under `~/loopany-src/skills/` on demand. The injection only adds one persistent rule: before the final reply on user-requested work, read `~/loopany-src/skills/loopany-resolver/SKILL.md`.

Canonical snippet (`injections/resolver-memory.md`):

```markdown
## loopany skill resolver

Dispatcher lives at `~/loopany-src/skills/loopany-resolver/SKILL.md`.

**At the end of every user-requested task**, before the final message
where you'd normally report "done" / "shipped" / "here is the answer",
**Read `~/loopany-src/skills/loopany-resolver/SKILL.md` first**, then reply.
```

Adjust `~/loopany-src` if you cloned elsewhere; the injection path must match your checkout.

### Coding CLIs (Claude Code, Codex)

<Tabs>
<Tab title="Claude Code (user-wide)">

Appends once (idempotent `grep` guard):

```bash
grep -q "loopany skill resolver" ~/.claude/CLAUDE.md 2>/dev/null || \
  cat ~/loopany-src/injections/resolver-memory.md >> ~/.claude/CLAUDE.md
```

</Tab>
<Tab title="Codex">

```bash
TARGET="$HOME/AGENTS.md"
grep -q "loopany skill resolver" "$TARGET" 2>/dev/null || \
  cat ~/loopany-src/injections/resolver-memory.md >> "$TARGET"
```

</Tab>
<Tab title="Claude Code (per-project)">

Append to `./CLAUDE.md` in the project root, or to `~/.claude/projects/<slug>/memory/MEMORY.md` where `<slug>` is the project absolute path with `/` replaced by `-` (e.g. `/Users/x/dev/y` → `-Users-x-dev-y`).

</Tab>
</Tabs>

### Agent platforms (Hermes, OpenClaw, …)

Register the same text via the host memory primitive:

```bash
/memory add "$(cat ~/loopany-src/injections/resolver-memory.md)"
```

If multi-line values are rejected, collapse newlines: `tr '\n' ' ' < ~/loopany-src/injections/resolver-memory.md`.

### Verify injection

File checks (concrete, not self-query):

```bash
grep -l "loopany skill resolver" ~/.claude/CLAUDE.md ~/AGENTS.md 2>/dev/null
test -f ~/loopany-src/skills/loopany-resolver/SKILL.md && echo "RESOLVER present"
```

Start a **fresh** session and ask the agent where the loopany resolver lives. A correct answer cites `~/loopany-src/skills/loopany-resolver/SKILL.md` without searching — otherwise the memory file exists but did not load (check host memory scope rules).

<Info>
Re-running the `grep -q … || cat …` install is a no-op when the marker string is already present. `git pull` in `~/loopany-src` updates skill content; you do not need to re-inject unless the pointer path changed. A `doctor` check for resolver registration is noted as roadmap, not implemented yet.
</Info>

## Runtime integration model

```mermaid
flowchart LR
  subgraph repo ["~/loopany-src (checkout)"]
    CLI["src/cli.ts / loopany bin"]
    SK["skills/loopany-*"]
    INJ["injections/resolver-memory.md"]
  end
  subgraph ws ["$LOOPANY_HOME"]
    CFG["config.yaml"]
    KD["kinds/*.md"]
    ART["artifacts/**"]
    REF["references.jsonl"]
  end
  subgraph host ["Agent host memory"]
    MEM["CLAUDE.md / AGENTS.md / /memory"]
  end
  CLI -->|"loopany init"| ws
  INJ -->|"append once"| MEM
  MEM -.->|"Read resolver before final reply"| SK
  SK -->|"loopany artifact *"| CLI
  CLI --> ws
```

## Post-install: onboarding and cadence

After `init` with no mission artifacts:

1. Read `ONBOARDING.md` and run the conversation (creates `self` person + active `mission` artifacts).
2. Choose cadence **without asking the user** on coding CLIs: default is session-boundary prompts (`loopany followups --due today`, weekly sweep/reflect proposals) rather than host cron — Claude Code recurring cron expires after ~7 days per `INSTALL_FOR_AGENTS.md`.

`loopany doctor` treats incomplete onboarding as **fail** until `self` exists and at least one `mission` has `status: active`.

## Verify installation

```bash
loopany doctor
```

| Exit code | Meaning |
|-----------|---------|
| `0` | All non-fail checks passed |
| `1` | At least one check failed (schema version, kinds parse, artifacts, references, onboarding, …) |

Machine-readable report:

```bash
loopany doctor --format json
```

Typical checks after a full agent install: `workspace`, `schema version`, `kinds`, `artifacts`, `references`, `onboarding` (and warnings for `mission coverage`, `domain coverage`).

### Schema version mismatch

If `config.yaml#schemaVersion` lags the binary’s `SCHEMA_VERSION`, normal commands raise `SchemaVersionMismatchError` and point at `loopany migrate`. `doctor` and `migrate` bypass the guard to report or fix the workspace.

<ParamField body="LOOPANY_SKIP_VERSION_CHECK" type="string">
Set to `1` to skip schema guard (migration scripts and tests).
</ParamField>

## Environment variables

| Variable | Default | Role |
|----------|---------|------|
| `LOOPANY_HOME` | `~/loopany` | Workspace root for all CLI operations |
| `LOOPANY_SKIP_VERSION_CHECK` | unset | When `1`, skips `schemaVersion` vs binary check |
| `LOOPANY_EDITOR` | OS default | Optional editor override for `factory` UI (see `src/ui/editor.ts`) |

## Troubleshooting

| Symptom | Likely cause | Fix |
|---------|----------------|-----|
| `loopany: command not found` | Bun bin not on `PATH` | Add `$HOME/.bun/bin` to shell rc |
| `No loopany workspace at …` | Missing `init` or wrong `LOOPANY_HOME` | `export LOOPANY_HOME=…` then `loopany init` |
| `SchemaVersionMismatchError` | Stale workspace | `loopany migrate` per error message |
| Resolver not followed | Injection missing or wrong scope | Re-run idempotent append; fresh session; verify `grep` |
| `doctor` onboarding fail | No `self` / no active mission | Complete `ONBOARDING.md` Phase 3 |
| Kind file missing after init | Init skipped copy because file existed | Compare `$LOOPANY_HOME/kinds/` with `skills/loopany-core/kinds/` manually |

## Related pages

<CardGroup>
<Card title="Overview" href="/overview">
What loopany exposes (CLI, workspace, skills) and the shortest path from init to a mission-backed brain.
</Card>
<Card title="Quickstart" href="/quickstart">
First session: mission, task, reference edge, and doctor.
</Card>
<Card title="Workspace setup" href="/workspace-setup">
Post-init verification, onboarding completion, and cadence registration.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`config.yaml` keys and environment variables in depth.
</Card>
<Card title="Doctor and troubleshooting" href="/doctor-and-troubleshooting">
Full doctor check list and recovery steps.
</Card>
<Card title="Skills library" href="/skills-library">
Resolver routing table and skill pack layout.
</Card>
</CardGroup>

---

## 03. Quickstart

> First successful session: init workspace, create a mission and task, add a reference edge, run doctor, and optional reindex/search or factory UI.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/03-quickstart.md
- Generated: 2026-06-05T18:56:50.466Z

### Source Files

- `src/cli.ts`
- `src/commands/init.ts`
- `src/commands/artifact-create.ts`
- `src/commands/doctor.ts`
- `skills/loopany-core/kinds/mission.md`
- `skills/loopany-core/kinds/task.md`
- `test/scenario.e2e.test.ts`

---
title: "Quickstart"
description: "First successful session: init workspace, create a mission and task, add a reference edge, run doctor, and optional reindex/search or factory UI."
---

`loopany init` scaffolds a workspace at `$LOOPANY_HOME` (default `~/loopany`), copies bundled kind definitions from `skills/loopany-core/kinds/`, and writes `config.yaml` with the current `schemaVersion`. Every other command in this guide assumes that workspace exists and that at least one `mission` with `status: active` plus a `self` person artifact are present — without them, `loopany doctor` fails the onboarding check and `init` prints a pointer to `ONBOARDING.md`.

## Prerequisites

| Requirement | Notes |
|-------------|-------|
| [Bun](https://bun.sh) | Runtime for `src/cli.ts` and tests |
| `loopany` on `PATH` | From repo: `bun install` then `bun link`, or `bun run src/cli.ts` |
| Writable `$LOOPANY_HOME` | Override with `export LOOPANY_HOME=/path/to/workspace` for isolated runs |

<Note>
CLI commands that mutate or query artifacts print **JSON on stdout** (one object or array). Human-readable text appears only for `init`, `doctor` (default format), `factory`, and `--help`.
</Note>

## Session overview

```mermaid
sequenceDiagram
  participant You
  participant CLI as loopany CLI
  participant WS as $LOOPANY_HOME
  participant Graph as references.jsonl

  You->>CLI: init
  CLI->>WS: kinds/, artifacts/, config.yaml
  You->>CLI: artifact create (person, mission, task)
  CLI->>WS: artifacts/people/self.md, missions/*.md, tasks/*.md
  You->>CLI: refs add
  CLI->>Graph: append edge
  You->>CLI: doctor
  CLI->>WS: validate kinds, artifacts, graph, onboarding
  opt Search / UI
    You->>CLI: reindex
    CLI->>WS: search.db
    You->>CLI: search / factory
  end
```

After a successful pass, the workspace holds markdown artifacts under `artifacts/<dirName>/<id>.md`, hard edges in `references.jsonl`, and operational lines in `audit.jsonl`.

## Initialize the workspace

<Steps>
<Step title="Run init">

```bash
loopany init
```

Creates (if missing): workspace root, `kinds/`, `artifacts/`, and `config.yaml` with `schemaVersion` matching the binary. Copies any bundled `*.md` kind files not already present in `kinds/`.

<RequestExample>

```bash
loopany init
```

</RequestExample>

<ResponseExample>

```text
Initialized loopany workspace at /Users/you/loopany
Created 12 file(s).

NEXT — read ONBOARDING.md and start the onboarding conversation.
Without an active mission artifact, the brain has no reason to exist.
```

</ResponseExample>

The onboarding hint appears when `artifacts/missions/` has no `.md` files yet. Re-running `init` is idempotent — existing files are left untouched.

</Step>
<Step title="Confirm layout">

```text
$LOOPANY_HOME/
  config.yaml
  kinds/           # mission.md, task.md, person.md, …
  artifacts/       # empty until you create artifacts
  audit.jsonl      # written after first mutating command
```

Bundled kinds after init include: `brief`, `journal`, `learning`, `mission`, `note`, `person`, `signal`, `skill-proposal`, `task`.

</Step>
</Steps>

For a disposable workspace (matches e2e tests):

```bash
export LOOPANY_HOME="$(mktemp -d)"
loopany init
```

## Minimum viable brain

Doctor’s **onboarding** check requires:

1. A `person` artifact with id `self`
2. At least one `mission` with `status: active`

<Steps>
<Step title="Create self">

```bash
loopany artifact create \
  --kind person \
  --slug self \
  --name "Your Name"
```

<ResponseField name="stdout" type="object">
JSON with `id`, `kind`, `path`. For `--slug self`, `id` is `self` and the file is `artifacts/people/self.md`.
</ResponseField>

</Step>
<Step title="Create an active mission">

```bash
loopany artifact create \
  --kind mission \
  --slug ship-v1 \
  --title "Ship loopany v1" \
  --status active \
  --hypothesis "Dogfood CLI daily and fix friction as it appears"
```

Required frontmatter for `mission`: `title` (string), `status` (`active` | `paused` | `satisfied` | `abandoned`). Optional: `hypothesis`, `domain`.

Mission files live at `artifacts/missions/<slug>.md`. The slug becomes the global artifact id used in `refs`, `mentions`, and `[[wiki-links]]`.

</Step>
<Step title="Create a task linked to the mission">

```bash
loopany artifact create \
  --kind task \
  --slug quickstart-smoke \
  --title "Verify loopany quickstart path" \
  --status todo \
  --priority medium \
  --mentions ship-v1
```

`task` defaults `status` to `todo` if omitted. `mentions` is a comma-separated list of artifact ids (here, the mission slug).

</Step>
</Steps>

<ParamField body="--slug" type="string">
Stable id for citations. Omitted: derived from `--title` with collision suffixes, or a time-based slug when no title is usable.
</ParamField>

<ParamField body="--content / --content-file" type="string">
Optional markdown body at create time. Use `--content-file -` to read stdin.
</ParamField>

Field flags are per-kind. Unknown flags produce an error listing valid fields for that kind (see `~/loopany/kinds/<kind>.md` or `loopany kind list`).

## Add a reference edge

Hard graph edges are append-only records in `references.jsonl`, written by the CLI (not by editing artifact bodies alone).

Link a task to the mission with an explicit edge (in addition to `mentions` in frontmatter, which the index also treats as soft links):

```bash
loopany refs add \
  --from quickstart-smoke \
  --to ship-v1 \
  --relation mentions
```

<ResponseExample>

```json
{
  "from": "quickstart-smoke",
  "to": "ship-v1",
  "relation": "mentions",
  "ts": "2026-06-05T12:00:00.000Z",
  "actor": "cli"
}
```

</ResponseExample>

Query outgoing edges:

```bash
loopany refs quickstart-smoke
```

Common relations (open registry; these are conventions): `led-to`, `addresses`, `mentions`, `supersedes`, `follows-up`, `cites`. See the relations convention skill for direction rules — e.g. `led-to` runs **from** cause **to** effect (`signal` → `task`).

## Verify with doctor

```bash
loopany doctor
```

Exit code `0` only when no check has status `fail`. Typical human output:

```text
Workspace       /Users/you/loopany ✓
Schema version  v0.2.0 ✓
Kinds           9 loaded (brief, journal, …) ✓
Artifacts       3 total, all valid ✓
References      1 edges, no dangling ✓
Onboarding      self present, 1 active mission(s) ✓
Mission coverage all tasks mention a mission ✓
Domain coverage enabled: [none] ✓
```

| Check | Fail vs warn |
|-------|----------------|
| `workspace`, `schema version`, `kinds`, `artifacts`, `references`, `onboarding` | **fail** — fix before relying on the brain |
| `mission coverage` | **warn** — tasks without `mentions` pointing at a mission id |
| `domain coverage` | **warn** — artifact `domain` not in `enabled_domains` |

JSON report:

```bash
loopany doctor --format json
```

Doctor runs with schema version check bypassed so it can **report** mismatches and suggest `loopany migrate …` instead of crashing.

<Warning>
A fresh `init` workspace fails onboarding until `self` and an active `mission` exist. That is expected — not a broken install.
</Warning>

## Optional: search and factory

### Hybrid search

Search uses a derived SQLite index at `$LOOPANY_HOME/search.db`. Creates and appends do **not** auto-update the index; run `reindex` after bulk writes or when search feels stale.

```bash
loopany reindex --no-embed    # fast CI-style index; keyword FTS only
loopany reindex               # full index with embeddings (first run loads model)
loopany search "quickstart" --kind task --limit 5
```

If `search.db` is missing, `loopany search` returns `[]` and prints on stderr: `no search index found — run loopany reindex first.`

| Flag | Effect |
|------|--------|
| `--force` | Delete and rebuild `search.db` |
| `--no-embed` | Skip embedding model; FTS keyword path only |

### Factory UI

Read-only walkable view of the workspace:

```bash
loopany factory --port 4242 --no-open
```

Defaults: host `127.0.0.1`, port `4242`, opens browser on macOS/Windows/Linux unless `--no-open`. Ctrl-C stops the server.

## Complete copy-paste script

Sets `LOOPANY_HOME` to a temp dir, runs the full minimal path, and leaves doctor green:

```bash
export LOOPANY_HOME="$(mktemp -d)"
loopany init

loopany artifact create --kind person --slug self --name "Quickstart User"
loopany artifact create \
  --kind mission --slug ship-v1 \
  --title "Ship loopany v1" --status active
loopany artifact create \
  --kind task --slug quickstart-smoke \
  --title "Verify loopany quickstart path" \
  --status todo --mentions ship-v1

loopany refs add --from quickstart-smoke --to ship-v1 --relation mentions
loopany doctor
```

Inspect artifacts:

```bash
loopany artifact get ship-v1
loopany artifact list --kind task --status todo
```

## Agent-led onboarding

If you use an agent harness instead of hand-typing commands, point it at `ONBOARDING.md` after `init`. That script creates `self`, one or more active missions, and optional backfill artifacts — the same end state this quickstart builds manually. Do not run onboarding twice when an active mission already exists.

## Troubleshooting

| Symptom | Likely cause | Action |
|---------|----------------|--------|
| `Error: … run loopany init` | `$LOOPANY_HOME` missing or empty | `loopany init` or set `LOOPANY_HOME` |
| `Invalid input:` + Zod paths | Wrong `--field` for kind | `loopany kind list`; read `kinds/<kind>.md` |
| Doctor onboarding ✗ | No `self` or no active mission | Create artifacts above |
| Doctor schema version ✗ | Workspace older than binary | `loopany migrate` (see migration docs) |
| `Unknown field for kind X` | Typo in flag name | Use `--check-at` not `--checkAt` (kebab-case flags) |
| Search always empty | No index | `loopany reindex` |
| `refs add` error | Missing `--from` / `--to` / `--relation` | All three required |

<Info>
`LOOPANY_SKIP_VERSION_CHECK=1` bypasses the schema guard for emergency inspection; prefer `loopany migrate` for real version drift.
</Info>

## What you have now

```text
artifacts/
  people/self.md
  missions/ship-v1.md
  tasks/quickstart-smoke.md
references.jsonl    # mentions edge(s)
audit.jsonl         # init, creates, refs, doctor ops
config.yaml         # schemaVersion, enabled_domains (when set)
```

Next operational steps (not required for “first success”): complete a task with `artifact append --section Outcome` then `artifact status <id> done`, run `loopany followups --due today` when `checkAt` is set, or wire the resolver skill into your agent host per the installation page.

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Clone, link the CLI, set `PATH` and `$LOOPANY_HOME`, and attach the resolver to your agent host.
</Card>
<Card title="Artifacts and workspace" href="/artifacts-and-workspace">
On-disk layout, slug-as-id rules, and immutable write semantics.
</Card>
<Card title="Reference graph" href="/reference-graph">
Relation verbs, `refs`/`trace` queries, and wiki-link conventions.
</Card>
<Card title="Artifact lifecycle example" href="/artifact-lifecycle-example">
Signal → task → outcome flow aligned with `test/scenario.e2e.test.ts`.
</Card>
<Card title="Doctor and troubleshooting" href="/doctor-and-troubleshooting">
Full check list, JSON format, and recovery patterns.
</Card>
<Card title="CLI reference" href="/cli-reference">
Complete command surface and stdout conventions.
</Card>
</CardGroup>

---

## 04. Artifacts and workspace

> On-disk layout under $LOOPANY_HOME: artifacts/<dirName>/<slug>.md, config.yaml, references.jsonl, audit.jsonl, optional search.db, and v0.2 slug-as-id storage rules.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/04-artifacts-and-workspace.md
- Generated: 2026-06-05T18:57:39.562Z

### Source Files

- `src/core/artifact-store.ts`
- `src/core/engine.ts`
- `src/commands/init.ts`
- `src/core/config.ts`
- `src/core/audit.ts`
- `skills/loopany-core/kinds/journal.md`
- `INSTALL_FOR_AGENTS.md`

---
title: "Artifacts and workspace"
description: "On-disk layout under $LOOPANY_HOME: artifacts/<dirName>/<slug>.md, config.yaml, references.jsonl, audit.jsonl, optional search.db, and v0.2 slug-as-id storage rules."
---

The loopany workspace is a single directory tree (default `~/loopany`, overridable via `LOOPANY_HOME`) where markdown artifacts are the source of truth, append-only JSONL files record graph edges and CLI operations, and an optional SQLite `search.db` is a derived index rebuilt by `loopany reindex`.

## Workspace root

<ParamField body="LOOPANY_HOME" type="path">
Overrides the default workspace location. When unset, the root is `~/loopany` (from `os.homedir()`).
</ParamField>

Bootstrap treats a directory as a workspace when `kinds/` exists. If not, commands throw `WorkspaceNotFoundError` with a pointer to `loopany init`.

| Path | Created by | Role |
|------|------------|------|
| `config.yaml` | `loopany init` (if missing) | `schemaVersion`, `enabled_domains` |
| `kinds/*.md` | `loopany init` copies bundled defs | Kind schemas, status machines, `dirName` / `slugLayout` |
| `artifacts/` | `loopany init` | All artifact markdown files |
| `domains/<name>/kinds/` | Agent / user (not init) | Domain-scoped kind overrides when domain enabled |
| `references.jsonl` | First `refs add` or hand edit | Append-only explicit graph edges |
| `audit.jsonl` | First CLI command after workspace exists | Append-only operational log |
| `search.db` | `loopany reindex` | Derived hybrid search index (safe to delete) |

`loopany init` is idempotent: it creates missing directories and `config.yaml`, copies any bundled kind files from `skills/loopany-core/kinds/` that are not already present, and leaves existing files untouched.

<RequestExample>

```bash
loopany init
```

</RequestExample>

<ResponseExample>

```text
Workspace: /Users/you/loopany
Created: config.yaml, kinds/task.md, ...
Needs onboarding: true   # when artifacts/missions/ has no .md files
```

</ResponseExample>

## Directory layout (v0.2)

:::files
$LOOPANY_HOME/
├── config.yaml
├── kinds/                    # global kind definitions
├── artifacts/
│   ├── tasks/                # flat: artifacts/<dirName>/<slug>.md
│   ├── signals/
│   ├── briefs/
│   ├── learnings/
│   ├── skill-proposals/
│   ├── missions/
│   ├── notes/
│   ├── people/
│   └── journal/
│       └── 2026/             # year layout only
│           └── 2026-06-05.md
├── domains/                  # optional scope packs
│   └── <name>/kinds/
├── references.jsonl
├── audit.jsonl
└── search.db                 # optional; from reindex
:::

<Note>
v0.1 used date buckets such as `artifacts/2026-06/tsk-*.md` and kind-prefixed IDs (`tsk-`, `sig-`). Current binaries expect `schemaVersion: 0.2.0` and flat `artifacts/<dirName>/` paths. See [Schema migration](/schema-migration) if bootstrap reports a version mismatch.
</Note>

### Kind → `dirName` mapping

Each kind definition in `kinds/*.md` (or `domains/<name>/kinds/*.md`) declares storage under `artifacts/`. The registry parses `dirName` and `slugLayout` from kind frontmatter.

| Kind | `dirName` | `slugLayout` | Notes |
|------|-----------|--------------|-------|
| `task` | `tasks` | `flat` (default) | Default `dirName` = `{kind}s` |
| `signal` | `signals` | `flat` | |
| `brief` | `briefs` | `flat` | |
| `learning` | `learnings` | `flat` | |
| `skill-proposal` | `skill-proposals` | `flat` | Hyphenated kind → `{kind}s` |
| `mission` | `missions` | `flat` | Explicit in kind file |
| `note` | `notes` | `flat` | |
| `person` | `people` | `flat` | |
| `journal` | `journal` | `year` | `artifacts/journal/<YYYY>/<YYYY-MM-DD>.md` |

`slugLayout: year` adds a four-digit year subdirectory derived from the first four characters of the artifact id (used by `journal`, whose id is the calendar date).

```mermaid
flowchart TB
  subgraph workspace["$LOOPANY_HOME"]
    config["config.yaml"]
    kinds["kinds/*.md"]
    arts["artifacts/"]
    refs["references.jsonl"]
    audit["audit.jsonl"]
    search["search.db optional"]
  end
  subgraph engine["bootstrap()"]
    KR["KindRegistry.load"]
    AS["ArtifactStore"]
    RG["ReferenceGraph"]
  end
  kinds --> KR
  KR --> AS
  arts --> AS
  refs --> RG
  config --> engine
  AS --> arts
```

## Slug-as-id rules (v0.2)

The filename stem **is** the artifact id. There is no kind prefix in ids or paths. Global uniqueness is enforced at create time by scanning every registered kind directory for `<id>.md`.

### Allocation order

On `artifact create`, the store picks an id in this order:

1. **Caller slug** — `--slug` (validated, must not exist).
2. **Title-derived** — `slugifyTitle(title)` from validated frontmatter; collisions get `-2`, `-3`, … up to 99 attempts.
3. **Timestamp fallback** — `YYYYMMDD-HHMMSS-<3hex>` UTC when title slugify fails (emoji-only titles, empty slugify, or collision exhaustion).

### Slug validation

| Rule | Constraint |
|------|------------|
| Length | 1–60 Unicode codepoints |
| Charset | Letters, marks, digits, `-`, `_` (NFC-normalized) |
| Edges | No leading/trailing `-` or `_`; no `--` or `__` |
| Uniqueness | Global across all kinds (store-enforced) |

Wiki links and CLI commands use this bare slug: `loopany artifact get reddit-claude-design`, `[[reddit-claude-design]]` in body text.

### Resolution and timestamps

- **`get(id)`** — Probes each kind’s storage path until a file exists (~10 kinds, cheap stat calls).
- **`createdAt` / `updatedAt`** — Stamped on every write; `createdAt` set on create if omitted.
- **Built-in frontmatter fields** — `domain`, `createdAt`, `updatedAt`, `_backfilled` (migration marker) bypass per-kind field specs.

## Artifact file format

Each artifact is one markdown file: YAML frontmatter + body. Kind-specific frontmatter is validated with Zod schemas built from the kind’s `## Frontmatter` YAML block.

**Mutations** (enforced by store / CLI):

| Operation | Effect |
|-----------|--------|
| `artifact append` | Appends a `## Section` block to the body |
| `artifact status` | Updates `status` via the kind’s state machine |
| `artifact set` | Updates a non-status frontmatter field |
| In-place body rewrite for “cited” kinds | Avoided — append, status flip, or supersede |

<Warning>
Do not use `artifact set` on `status`; use `artifact status` so transitions are validated against the kind’s status machine.
</Warning>

### Journal (auto-managed spine)

The `journal` kind is not created via `artifact create --kind journal` (rejected at CLI). The store lazily creates `artifacts/journal/<YYYY>/<YYYY-MM-DD>.md` when the first non-journal artifact is created that day, and appends a wiki-link line to `## Activity` (or `## Backfilled` when `_backfilled: true`).

Journal ids equal the date string (`2026-06-05`). The year subdirectory is for human browsing only; resolution still uses the global slug.

## `config.yaml`

<ParamField body="schemaVersion" type="string">
Workspace format version. New workspaces get the binary’s `SCHEMA_VERSION` (`0.2.0`). If absent, assumed `0.1.0` (legacy). Mismatch with the binary throws `SchemaVersionMismatchError` unless `LOOPANY_SKIP_VERSION_CHECK=1` or bootstrap `skipVersionCheck`.
</ParamField>

<ParamField body="enabled_domains" type="string[]">
Domain names whose `domains/<name>/kinds/` packs are merged into the kind registry at boot.
</ParamField>

## `references.jsonl`

Append-only graph log at the workspace root. One JSON object per line:

```json
{"ts":"2026-06-05T12:00:00.000Z","from":"<slug>","to":"<slug>","relation":"<verb>","actor":"cli"}
```

`relation` is an open registry (conventions in skills, not a closed enum). Reverse edges are built in memory at load time, not stored.

**Implicit edges** — At index build, `mentions` in frontmatter and `[[id]]` wiki links in the body synthesize `mentions` edges in memory only. They do not append rows to `references.jsonl`; updating the artifact updates the implicit graph on the next boot.

## `audit.jsonl`

Every CLI invocation appends one row after dispatch:

```json
{"ts":"...","op":"artifact.create","actor":"cli","duration_ms":42}
```

Optional `error` field when the command failed. Audit write failures are silent so they never break user-facing operations.

## `search.db` (optional)

| Property | Detail |
|----------|--------|
| Location | `$LOOPANY_HOME/search.db` |
| Engine | Bun SQLite |
| Source of truth | Markdown artifacts only |
| Population | `loopany reindex` (optional `--force`) |
| Missing DB | `loopany search` returns empty results and stderr hint to run reindex |

The index chunks artifact bodies/titles, uses FTS5 + optional embeddings fused by reciprocal rank fusion. Deleting `search.db` does not lose artifacts; rebuild with reindex.

## Engine composition

`bootstrap()` wires the workspace for command handlers:

| Module | Path / object | Responsibility |
|--------|---------------|----------------|
| `Config` | `config.yaml` | Schema version, enabled domains |
| `KindRegistry` | `kinds/` + domain packs | Parse kind defs, Zod validators, status machines |
| `ArtifactStore` | `artifacts/` | Create, read, list, append, set field/status |
| `ReferenceGraph` | `references.jsonl` | Append/load explicit edges |
| `ArtifactIndex` | In-memory | Built per command via `engine.index()` — filters, refs, implicit mentions |

Storage (flat artifact pool) and organization (`domains/` curated views) are separate: one artifact can be tagged with `domain` frontmatter and referenced from multiple domain scopes without duplicating files.

## v0.1 → v0.2 migration summary

When `config.yaml#schemaVersion` is older than the binary’s `SCHEMA_VERSION`, bootstrap refuses normal operations and points at the migration skill and `loopany migrate`.

| v0.1 | v0.2 |
|------|------|
| `tsk-20260427-103045` ids | `20260427-103045` slug ids |
| `artifacts/{YYYY-MM}/...` buckets | `artifacts/<dirName>/<slug>.md` |
| `check_at`, snake_case FM | `checkAt`, camelCase FM |
| No journal | Auto `journal/<YYYY>/<date>.md` + backfill |
| Kind knobs: `idPrefix`, `storage`, `bodyMode` | Dropped; optional `slugLayout` only |

Migration scripts rewrite files, emit `.migration-id-map.json`, rebuild `references.jsonl`, and set `schemaVersion: 0.2.0`.

<Steps>
<Step title="Detect mismatch">
Run any command; `SchemaVersionMismatchError` names workspace vs binary versions and the migrate subcommand.
</Step>
<Step title="Snapshot">
Git-commit or copy `$LOOPANY_HOME` before applying migration scripts (see migration skill pre-flight).
</Step>
<Step title="Migrate and verify">
`loopany migrate v0.1.0-to-v0.2.0` (or run scripted steps), then `loopany doctor` for schema version, artifact validation, and dangling refs.
</Step>
</Steps>

## Verification

```bash
loopany doctor
loopany kind list          # dirName + slugLayout per kind
loopany artifact list --kind task
```

Doctor checks: workspace presence, schema version, kind parse health, artifact frontmatter validity, reference endpoint integrity, onboarding (`self` person + active mission), and warnings for mission/domain coverage.

<Check>
Exit code `0` from `loopany doctor` means the on-disk workspace matches what the binary expects for v0.2 storage and graph integrity.
</Check>

## Related pages

<CardGroup>
<Card title="Workspace setup" href="/workspace-setup">
Run `loopany init`, verify bundled kinds, onboarding, and doctor.
</Card>
<Card title="Kinds and validation" href="/kinds-and-validation">
Kind markdown defs, Zod frontmatter, status machines, immutable write rules.
</Card>
<Card title="Reference graph" href="/reference-graph">
`references.jsonl`, implicit mentions, `refs` / `trace` queries.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`LOOPANY_HOME`, `LOOPANY_SKIP_VERSION_CHECK`, schema version guard.
</Card>
<Card title="Schema migration" href="/schema-migration">
v0.1.0→v0.2.0 scripts, id map, journal backfill.
</Card>
<Card title="Artifact commands" href="/artifact-commands">
`create` / `get` / `list`, `--slug`, append and status flows.
</Card>
</CardGroup>

---

## 05. Kinds and validation

> Markdown kind definitions, dynamic Zod frontmatter schemas, status machines, indexedFields, slug rules, and immutable write semantics (append, status flip, supersede).

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/05-kinds-and-validation.md
- Generated: 2026-06-05T18:58:01.888Z

### Source Files

- `src/core/kind-registry.ts`
- `skills/loopany-core/kinds/task.md`
- `skills/loopany-core/kinds/signal.md`
- `skills/loopany-core/kinds/note.md`
- `src/core/slug.ts`
- `src/commands/artifact-status.ts`
- `test/kind-registry.test.ts`

---
title: "Kinds and validation"
description: "Markdown kind definitions, dynamic Zod frontmatter schemas, status machines, indexedFields, slug rules, and immutable write semantics (append, status flip, supersede)."
---

Loopany treats **kind** as an open registry: each kind is a Markdown file under `$LOOPANY_HOME/kinds/` (plus optional domain packs). At bootstrap, `KindRegistry` parses those files into Zod frontmatter validators, optional status machines, storage layout (`dirName`, `slugLayout`), and `indexedFields` for queries. `ArtifactStore` applies that metadata on every create, append, field update, and status transition.

## Kind definition format

A kind file has two layers of frontmatter:

| Layer | Location | Purpose |
| --- | --- | --- |
| Top YAML | Between the first `---` pair | `kind`, optional `dirName`, `slugLayout`, `indexedFields` |
| Field spec | `## Frontmatter` → fenced `yaml` block | Per-field types, `required`, `default`, enum `values` |
| Status machine | `## Status machine` → fenced `yaml` (optional) | `initial` + `transitions` map |
| Agent guidance | `## Required sections`, `## UI`, playbook prose | Conventions for agents; not parsed by the runtime |

`parseKindDefinition` in `src/core/kind-registry.ts` splits the body on `##` headings, extracts YAML from fenced blocks, and builds a `KindDefinition`.

**Defaults when omitted:**

| Field | Default |
| --- | --- |
| `dirName` | `{kind}s` (e.g. `task` → `tasks`) |
| `slugLayout` | `flat` |
| `indexedFields` | `[]` |

**`slugLayout` values:**

| Value | On-disk path |
| --- | --- |
| `flat` | `artifacts/<dirName>/<id>.md` |
| `year` | `artifacts/<dirName>/<YYYY>/<id>.md` (year = first four characters of `id`; used by `journal`) |

Example top-level kind header (`skills/loopany-core/kinds/note.md`):

```yaml
---
kind: note
dirName: notes
indexedFields: [tags]
---
```

Bundled kinds ship from `skills/loopany-core/kinds/`; `loopany init` copies any missing `*.md` into `~/loopany/kinds/` without overwriting existing files.

## Loading and extending the registry

```text
bootstrap (src/core/engine.ts)
  ├─ KindRegistry.load($LOOPANY_HOME/kinds)
  └─ merge packDirs: domains/<enabled>/kinds/*.md
       └─ duplicate kind or dirName → LoadIssue (first wins)
```

<ParamField body="packDirs" type="string[]">
  One directory per enabled domain from `config.yaml` `enabled_domains`. Missing domain `kinds/` dirs are skipped.
</ParamField>

`loopany kind list` prints JSON: `kind`, `dirName`, `slugLayout`, `indexedFields`, `hasStatusMachine`.

<Warning>
Broken kind files do not crash bootstrap. They appear in `registry.issues` and fail the `kinds` check in `loopany doctor`.
</Warning>

## Dynamic Zod frontmatter schemas

`buildZodSchema` maps each `## Frontmatter` field spec to a Zod type:

| `type` in kind file | Zod | Notes |
| --- | --- | --- |
| `string` | `z.string()` | |
| `enum` | `z.enum([...])` | Requires non-empty `values` |
| `date` | `z.string()` | ISO date string; no deep date parsing |
| `bool` | `z.boolean()` | |
| `string[]` | `z.array(z.string())` | CLI `set` accepts JSON array or comma-separated |
| `number` | `z.number()` | |

Fields with `default` get `.default(...)`; fields without `required: true` are `.optional()`. The object schema uses `.passthrough()`, so extra frontmatter keys (for example `createdAt`, `updatedAt`, `_backfilled`) are allowed.

**Built-in fields** accepted on every kind without appearing in the kind spec: `domain`, `createdAt`, `updatedAt`, `_backfilled`.

Validation runs on:

- `ArtifactStore.create` — after auto-filling `status` from the machine `initial`
- `ArtifactStore.setField` — after coercion
- `loopany doctor` — `safeParse` over every artifact in the index

<RequestExample>

```bash
loopany artifact create --kind task --title "Fix cache" --status todo
```

</RequestExample>

Missing `title` on a required field or an invalid enum value throws a Zod error at create time.

## Status machines

Kinds with a `## Status machine` block get runtime enforcement in `ArtifactStore.setStatus`. The CLI routes status changes through `artifact status`, not `artifact set`.

```mermaid
stateDiagram-v2
  [*] --> todo: create (initial)
  todo --> running
  todo --> done
  todo --> cancelled
  running --> in_review
  running --> done
  running --> failed
  running --> cancelled
  in_review --> done
  in_review --> failed
  in_review --> cancelled
```

(Transitions match bundled `skills/loopany-core/kinds/task.md`.)

| Behavior | Implementation |
| --- | --- |
| Initial status on create | Set to `statusMachine.initial` when `status` omitted |
| Legal transition | `newStatus` must be in `transitions[current]` |
| Illegal transition | Error: `Illegal transition: <current> → <newStatus>` |
| Kinds without a machine | `setStatus` throws; use `setField` for frontmatter-only kinds like `note` |
| `--reason` on CLI | Accepted by `artifact status` but **not** written to the artifact body |

**Signal-specific rule:** transitioning to `addressed` requires `--addressed-by <id>`. The CLI appends a hard `addresses` edge from that artifact to the signal (`src/commands/artifact-status.ts`).

**Terminal detection:** `loopany followups` hides artifacts whose current `status` has no outgoing transitions in the kind machine (unless `--include-done true`).

Kinds without status machines include `note`, `person`, and `journal`.

## Required body sections (agent-enforced)

Many bundled kinds declare `## Required sections` (for example `task`: `## Outcome` before `done` or `failed`). **The TypeScript runtime does not parse or enforce these rules** — `setStatus` does not inspect the body. Compliance is expected from agents following kind playbooks and capture/reflect skills. `loopany doctor` validates frontmatter only, not body shape.

Recommended terminal flow for `task`:

```bash
loopany artifact append <id> --section Outcome --content "..."
loopany artifact status <id> done --reason "one-line summary"
```

## Slug rules (v0.2)

The artifact **id is the slug** — globally unique across all kinds, no kind prefix. Files live at `artifacts/<dirName>/[<YYYY>/]<id>.md`.

`validateSlug` / `requireValidSlug` (`src/core/slug.ts`):

| Rule | Constraint |
| --- | --- |
| Length | 1–60 Unicode codepoints |
| Charset | Letters (`\p{L}`), marks (`\p{M}`), digits (`\p{N}`), `-`, `_` |
| Edges | No leading/trailing `-` or `_` |
| Runs | No `--` or `__` |
| Normalization | NFC before store/compare |

**ID allocation order** (`ArtifactStore.allocateId`):

1. Explicit `--slug` (validated, must not exist globally)
2. `slugifyTitle(title)` — lowercased, punctuation collapsed to `-`, max 40 codepoints; collisions get `-2`, `-3`, …
3. `generateFallbackSlug()` — `YYYYMMDD-HHMMSS-<3hex>` UTC when title slugify fails or is absent

<Info>
Cross-kind uniqueness is enforced by scanning every registered kind directory for `<id>.md` at create time.
</Info>

`note` and `person` playbooks expect human-chosen slugs (`project-phoenix`, `alice-chen`) for stable `[[wiki-links]]`.

## indexedFields

Declared in the kind file top frontmatter (for example `task`: `[status, priority, checkAt]`). At index build time, each listed field value is indexed per kind; array values index each element separately (`src/core/index.ts`).

Used by:

- `loopany artifact list --kind <K> --where <field>=<value>` when `<field>` is in that kind’s `indexedFields`
- In-memory `ArtifactIndex.byField(kind, field, value)`

Global indexes (not per-kind declaration): `status`, `domain`, `checkAt` (followups), kind name.

<Note>
`checkAt` is indexed for tasks but followups scans all artifacts with a `checkAt` frontmatter value regardless of kind.
</Note>

## Immutable write semantics

Loopany does not expose “edit artifact body in place” or “replace frontmatter wholesale.” Mutations are narrow operations:

```text
                    ┌─────────────────────────────────────┐
                    │  artifacts/<dirName>/[<Y>/]<id>.md  │
                    └─────────────────────────────────────┘
         create ──►│ frontmatter + body (initial)         │
         append ──►│ body: new H2 or append under H2      │  appendSection
         setField ─►│ frontmatter: one non-status field    │  re-validate Zod
         setStatus ►│ frontmatter: status only             │  machine check
         supersede ►│ NEW artifact + supersedes edge       │  (pattern, not store API)
                    │ OLD artifact: status flip only       │
```

| Operation | API | What changes |
| --- | --- | --- |
| Append body | `artifact append <id> --section <S> --content …` | Adds or extends `## <S>`; preserves other sections |
| Flip status | `artifact status <id> <status>` | `status` + `updatedAt`; body unchanged |
| Set field | `artifact set <id> --<field> <value>` | Single frontmatter field; **blocks `status`** |
| Supersede belief/version | Create new artifact + `refs add --relation supersedes` + `status` on old | Old file kept; graph records replacement (`learning`, `mission`, `brief` playbooks) |

`--reason` on status is for CLI/audit context only; it does not append a `## Status` section (see `test/artifact-store.test.ts`).

**Journal exception:** `journal` is auto-written by the store on other creates; `loopany artifact create --kind journal` is rejected at the CLI. Auto-append only touches `## Activity` (or `## Backfilled` for `_backfilled` artifacts).

**Entity kinds:** `person` documents append-only body timeline; frontmatter `name` / `aliases` may change via `setField`.

## Core bundled kinds (reference)

| Kind | `dirName` | Status machine | Notable `indexedFields` |
| --- | --- | --- | --- |
| `task` | `tasks` | todo → … → terminal | `status`, `priority`, `checkAt` |
| `signal` | `signals` | open / addressed / dismissed | `status` |
| `note` | `notes` | none | `tags` |
| `learning` | `learnings` | active / superseded / archived | `domain`, `status`, `checkAt` |
| `skill-proposal` | `skill-proposals` | pending → accepted/rejected | `status`, `targetSkill`, `domain` |
| `mission` | `missions` | yes | (see `kinds/mission.md`) |
| `brief` | `briefs` | yes | (see `kinds/brief.md`) |
| `person` | `people` | none | `aliases` |
| `journal` | `journal` | none; `slugLayout: year` | `date` |

## When to add a kind vs use `note`

Bundled `note` documents a four-question test: add a new kind only if you need a state machine, canonical identity/dedup, structured indexed queries, or a fixed body shape downstream code depends on. Otherwise use `note` or a domain scope (`skills/loopany-core/kinds/note.md`).

Domain-specific kinds belong under `domains/<name>/kinds/` and load after workspace kinds; conflicting `kind` or `dirName` values register as load issues.

## Verification

<Steps>
<Step title="List registered kinds">

```bash
loopany kind list
```

Expect bundled kinds with `hasStatusMachine` where applicable.

</Step>
<Step title="Run doctor">

```bash
loopany doctor
```

`kinds` and `artifacts` checks should pass on a healthy workspace.

</Step>
<Step title="Exercise validation">

```bash
loopany artifact create --kind task --title "Demo" --status bogus
```

Expect Zod/enum failure. Then create with `todo`, append `Outcome`, and `artifact status <id> running`.

</Step>
</Steps>

## Related pages

<CardGroup>
<Card title="Artifacts and workspace" href="/artifacts-and-workspace">
  v0.2 paths, `references.jsonl`, and slug-as-id storage layout.
</Card>
<Card title="Artifact commands" href="/artifact-commands">
  create, append, status, set flags and per-kind CLI fields.
</Card>
<Card title="Domains" href="/domains">
  Domain-scoped kind packs and `enabled_domains`.
</Card>
<Card title="Self-improvement loop" href="/self-improvement-loop">
  `## Outcome`, learnings, skill-proposals, and supersede flow.
</Card>
<Card title="Doctor and troubleshooting" href="/doctor-and-troubleshooting">
  Kind load failures, Zod errors, and recovery.
</Card>
</CardGroup>

---

## 06. Reference graph

> Append-only references.jsonl edges, implicit mentions from frontmatter, canonical relation verbs, refs/trace queries, and wiki-link [[id]] conventions.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/06-reference-graph.md
- Generated: 2026-06-05T18:58:21.309Z

### Source Files

- `src/core/references.ts`
- `src/commands/refs.ts`
- `src/commands/trace.ts`
- `skills/loopany-core/conventions/relations.md`
- `src/core/link-parser.ts`
- `test/references.test.ts`

---
title: "Reference graph"
description: "Append-only references.jsonl edges, implicit mentions from frontmatter, canonical relation verbs, refs/trace queries, and wiki-link [[id]] conventions."
---

The loopany reference graph lives at `$LOOPANY_HOME/references.jsonl` as an append-only edge log. At index build time, `ArtifactIndex` loads those rows, synthesizes `mentions` edges from frontmatter and body `[[slug]]` links, and exposes the merged graph through `loopany refs` (BFS neighborhood) and `loopany trace` (signed causal walk). Reverse adjacency is computed in memory; only forward rows are persisted.

## Storage and index model

```text
$LOOPANY_HOME/
  references.jsonl     # explicit edges only (append-only)
  artifacts/...        # implicit mention sources (frontmatter + body)

Index build (each CLI command that calls engine.index()):
  1. ReferenceGraph.load() → forward + reverse maps from JSONL
  2. For each artifact:
       - frontmatter mentions: [...]  → implicit mentions edge
       - body [[slug]] via extractLinks → implicit mentions edge
  3. ArtifactIndex.refsOut / refsIn query the merged maps
```

<Note>
`relation` is an open registry in code — any string is accepted on `refs add`. Searchability depends on using the six convention verbs documented in `skills/loopany-core/conventions/relations.md`; synonyms fragment `refs` and `trace` results.
</Note>

### Explicit edge row shape

Each `refs add` or runtime append writes one JSON object per line:

| Field | Type | Meaning |
|-------|------|---------|
| `ts` | ISO-8601 string | Set automatically on append |
| `from` | artifact id (slug) | Source node |
| `to` | artifact id (slug) | Target node |
| `relation` | string | Verb (convention: see table below) |
| `actor` | string | Who wrote the edge (`cli`, `agent`, …) |

`ReferenceGraph.load()` skips blank lines and malformed JSON; rows missing `from`, `to`, or `relation` are ignored. Reverse edges are **not** stored — `ArtifactIndex` mirrors each row into both `forwardRefs` and `reverseRefs`.

### Implicit `mentions` edges

Implicit edges are **not** written to `references.jsonl`. They are rebuilt on every index from artifact content:

| Source | `actor` | `relation` | `ts` |
|--------|---------|------------|------|
| `mentions: [id, …]` in frontmatter | `frontmatter` | `mentions` | source file mtime |
| `[[slug]]` in body (outside code) | `body` | `mentions` | source file mtime |

All implicit edges carry `"implicit": true`. Editing frontmatter or body updates the graph on the next index without a `refs add` call. Query APIs return implicit and explicit edges together; filter by `implicit` or `actor` in JSON output when you need to distinguish them.

## Canonical relation verbs

Direction is always **A → verb → B** (active voice from `from`). To traverse the inverse, use `loopany refs <B> --direction in --relation <verb>` instead of writing a second edge.

| Verb | `from` → `to` reads as | Use when |
|------|------------------------|----------|
| `led-to` | cause → effect | Downstream artifact produced from upstream (default causal link) |
| `addresses` | action → concern | Task/signal pattern: handler resolves the observation |
| `mentions` | source → entity | Soft reference (frontmatter, `[[id]]`, or explicit add) |
| `supersedes` | new → old | Replacement artifact after mission/architecture shift |
| `follows-up` | continuation → original | Thread picked up later (often paired with `checkAt` tasks) |
| `cites` | summary → evidence | Brief or learning drawing on source artifacts |

<Warning>
Do not store both directions for the same fact (for example `led-to` plus `caused-by`). `trace` defaults exclude `caused-by`; use `refs --direction in` on the effect node instead.
</Warning>

### `addresses` via signal status

Closing a signal with responsibility emits a persisted `addresses` edge in one step:

```bash
loopany artifact status <signal-slug> addressed --addressed-by <task-slug>
```

The edge is `from: <task-slug>, to: <signal-slug>, relation: addresses` (action handles observation). `--addressed-by` is required for `addressed` and rejected for other statuses.

## Wiki-link `[[id]]` conventions

Body wiki links are parsed by `extractLinks()`:

- Pattern: `[[<slug>]]` where `<slug>` matches `^[\p{L}\p{M}\p{N}\-_]+$` (Unicode letters, marks, numbers, hyphen, underscore).
- No internal whitespace; characters like `.`, `/`, `:` are rejected.
- Fenced code blocks and inline `` `...` `` spans are stripped before matching so examples do not create edges.
- Every resolved link becomes an implicit `mentions` edge; **`[[id]]` cannot express `led-to`, `addresses`, or other verbs** — use `loopany refs add` for those.

Validation is deferred to index time: slug-shaped links to missing artifacts still appear as edges whose `to` id is unknown. `loopany doctor` reports them as dangling (`from → to (relation)`).

<Info>
Skill cross-references use `[[path/to/SKILL.md]]` in skill bodies. Those paths are **not** ingested into `references.jsonl` or the artifact graph; agents follow them manually. Artifact wiki links always target artifact slugs/ids, not skill file paths.
</Info>

## CLI: `refs`

### Add an explicit edge

```bash
loopany refs add --from <id> --to <id> --relation <verb>
```

<ResponseExample>

```json
{
  "ts": "2026-06-05T12:00:00.000Z",
  "from": "morning-signal",
  "to": "fix-cache-task",
  "relation": "led-to",
  "actor": "cli"
}
```

</ResponseExample>

`refs add` is recorded in `audit.jsonl` as `refs.add`.

### Query neighborhood (BFS)

```bash
loopany refs <id> [--direction in|out|both] [--relation <verb>] [--depth <N>] [--domain <name>]
```

<ParamField body="direction" type="string" default="out">
`out` — edges where `<id>` is `from`. `in` — edges where `<id>` is `to`. `both` — union of outgoing and incoming at each hop.
</ParamField>

<ParamField body="depth" type="positive integer" default="1">
Maximum hop count. Depth 1 is one-hop neighborhood; larger values walk chains but dedupe edges by `from|to|relation|ts`.
</ParamField>

<ParamField body="domain" type="string">
When set, keeps only edges where **both** endpoints have `frontmatter.domain` equal to the flag value.
</ParamField>

Returns a JSON array of edge objects (implicit and explicit). Visited-node deduplication prevents re-expanding a node, but all unique edges encountered within the depth budget are included.

## CLI: `trace`

`trace` walks **lineage predicates** to a fixed point, separate from the shallow `refs` neighborhood:

```bash
loopany trace <id> [--direction forward|backward|both] [--relations <csv>] [--max-depth <N>]
```

Default `--relations`: `led-to`, `addresses`, `supersedes`, `follows-up`, `cites`. **`mentions` is excluded** (soft pointer, not causal lineage). Override with `--relations mentions` when you need mention chains.

<ResponseExample>

```json
{
  "root": "fix-cache-task",
  "nodes": [
    { "id": "morning-signal", "kind": "signal", "distance": -1, "...": "..." },
    { "id": "fix-cache-task", "kind": "task", "distance": 0, "...": "..." },
    { "id": "cache-learning", "kind": "learning", "distance": 1, "...": "..." }
  ],
  "edges": [ "...Edge objects..." ]
}
```

</ResponseExample>

| Field | Meaning |
|-------|---------|
| `distance` | Signed hop count from root: negative = backward (causes), `0` = root, positive = forward (effects) |
| `nodes` | Sorted by `distance`, then `id`; dangling edge targets omitted |
| `edges` | All edges traversed, deduped by `from|to|relation|ts` |

`--direction forward` follows `refsOut`; `backward` follows `refsIn`. Default `both` walks both sides. Unknown root id fails with `No artifact with id: <id>`. Cycles terminate without duplicating nodes (first-seen distance wins).

## Explicit vs implicit: when to use which

```mermaid
flowchart LR
  subgraph persist["references.jsonl"]
    R1["refs add"]
    R2["artifact status --addressed-by"]
  end
  subgraph artifact["Artifact files"]
    F1["mentions: [...]"]
    F2["body [[slug]]"]
  end
  subgraph index["ArtifactIndex.build"]
    M["Merged forward/reverse maps"]
  end
  R1 --> persist
  R2 --> persist
  F1 --> index
  F2 --> index
  persist --> index
  index --> M
  M --> Q1["loopany refs"]
  M --> Q2["loopany trace"]
```

| Mechanism | Persisted | Best for |
|-----------|-----------|----------|
| `refs add --relation <verb>` | Yes | `led-to`, `addresses`, `supersedes`, `follows-up`, `cites` |
| `mentions:` frontmatter | No (implicit) | Structural attribution (mission, stakeholders) |
| `[[slug]]` in body | No (implicit) | In-prose name-drops |
| `artifact status … addressed --addressed-by` | Yes (`addresses`) | Signal-close workflow |

Explicit and implicit edges can coexist on the same pair with different relations (for example implicit `mentions` plus explicit `led-to` from the same task).

## Integrity and operations

`loopany doctor` checks reference integrity: every edge’s `to` (and orphan `from`) must resolve to an existing artifact in the index. Unresolved `[[typo-slug]]` targets count as dangling. Mission coverage warns when tasks lack a mission in `mentions`, but that is separate from the references check.

v0.1 → v0.2 migrations can rewrite `references.jsonl` ids to slug form (`skills/migrations/v0.1.0-to-v0.2.0/scripts/03-rebuild-references.ts` backs up the prior file). After migration, rebuild the index by running any command that loads the workspace.

The read-only **factory** UI (`loopany factory`) visualizes the same merged graph, rendering implicit edges with lower opacity than explicit `references.jsonl` rows.

## Anti-patterns

- **Duplicate direction edges** — store one canonical direction; query the inverse with `--direction in`.
- **Synonym verbs** (`triggered`, `resolves`, `references`, …) — use the six canonical verbs or extend `relations.md` deliberately.
- **`led-to` for everything** — prefer `addresses` for responsibility, `mentions` for referential links.
- **`[[id]]` for causal links** — always `refs add` with the intended verb.

## Related pages

<CardGroup>
  <Card title="Artifacts and workspace" href="/artifacts-and-workspace">
    On-disk layout for artifacts, references.jsonl, audit.jsonl, and v0.2 slug-as-id rules.
  </Card>
  <Card title="Graph, search, and scheduling" href="/graph-search-commands">
    Full refs/trace/followups command flags, factory UI, and audit side effects.
  </Card>
  <Card title="Artifact lifecycle example" href="/artifact-lifecycle-example">
    End-to-end signal → task with led-to/addresses edges and status transitions.
  </Card>
  <Card title="Doctor and troubleshooting" href="/doctor-and-troubleshooting">
    Dangling-edge detection, reference integrity checks, and recovery steps.
  </Card>
</CardGroup>

---

## 07. Domains

> Scope-local packs under domains/<name>/, enabled_domains in config.yaml, domain-scoped kind overrides, and when to propose a domain vs a note.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/07-domains.md
- Generated: 2026-06-05T18:58:13.529Z

### Source Files

- `src/core/config.ts`
- `src/commands/domain.ts`
- `src/core/engine.ts`
- `src/core/kind-registry.ts`
- `skills/loopany-core/conventions/taxonomy.md`
- `CLAUDE.md`

---
title: "Domains"
description: "Scope-local packs under domains/<name>/, enabled_domains in config.yaml, domain-scoped kind overrides, and when to propose a domain vs a note."
---

Domains in loopany combine two mechanisms: a **scope tag** on artifact frontmatter (`domain: <name>`) and optional **domain packs** under `$LOOPANY_HOME/domains/<name>/kinds/` whose kind definitions load only when the name appears in `config.yaml` → `enabled_domains`. Artifacts always live in the global `artifacts/` pool; enabling a domain changes validation and `loopany kind list`, not where files are stored.

## Two layers: tag vs pack

| Layer | What it is | Runtime effect |
|-------|------------|------------------|
| **Domain tag** | Optional `domain` frontmatter on any artifact (built-in field, not per-kind schema) | Indexed for `artifact list --domain`, `followups --domain`, `refs --domain`, `search --domain`; surfaced in factory graph payload |
| **Domain pack** | Directory `$LOOPANY_HOME/domains/<name>/kinds/*.md` | Extra kind definitions merged into `KindRegistry` when `<name>` is enabled |

Storage stays flat under `artifacts/<dirName>/<id>.md`. The product doc in `CLAUDE.md` also describes `manifest.yaml`, `settings.yaml`, `view.json`, and `daily/` under each domain — those paths are **design targets**, not loaded by the current CLI. Today only `domains/<name>/kinds/` is wired in `bootstrap()`.

```mermaid
flowchart TB
  subgraph workspace["$LOOPANY_HOME"]
    config["config.yaml\nenabled_domains"]
    globalKinds["kinds/*.md\ncore kinds"]
    artifacts["artifacts/**\nflat pool + domain tag"]
    pack["domains/{name}/kinds/*.md\ndomain kinds"]
  end
  bootstrap["bootstrap() in engine.ts"]
  registry["KindRegistry"]
  config --> bootstrap
  globalKinds --> bootstrap
  pack -->|"only if name ∈ enabled_domains"| bootstrap
  bootstrap --> registry
  registry --> artifacts
```

## Workspace layout

After `loopany init`, the workspace has `kinds/`, `artifacts/`, and `config.yaml`. Init does **not** create `domains/`; you add packs manually when a scope matures.

:::files
$LOOPANY_HOME/
  config.yaml                 # schemaVersion, enabled_domains
  kinds/                      # core kinds (always loaded)
  artifacts/                  # all artifacts; optional domain: in frontmatter
  domains/
    crm/
      kinds/
        deal.md               # example domain-only kind
:::

<Info>
One artifact can carry a `domain` tag without any pack directory existing. Conversely, you can enable a domain name before writing `domains/<name>/kinds/` — missing pack dirs are skipped silently at load time.
</Info>

## Configuration: `enabled_domains`

`Config` reads and writes `$LOOPANY_HOME/config.yaml`. The only domain-related key today is `enabled_domains` (YAML array of strings, sorted on write).

<ParamField body="enabled_domains" type="string[]">
Names of domain packs whose `kinds/` directories are merged at engine startup. Defaults to `[]` when absent.
</ParamField>

Fresh workspaces get `schemaVersion` only; domains start disabled:

```yaml
# loopany init default
schemaVersion: "0.2.0"
```

Example after enabling CRM and ads:

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

`enableDomain` / `disableDomain` are idempotent and persist immediately. There is **no** validation that `domains/<name>/` exists — enabling is purely a config flag until the next command bootstraps the registry.

## How domain packs load kinds

On every command, `bootstrap()` builds pack paths from enabled names and passes them to `KindRegistry.load()`:

1. Load all `kinds/*.md` under the workspace root (core kinds from init).
2. For each entry in `enabled_domains`, load `domains/<name>/kinds/*.md` if that directory exists.
3. Register each kind once; duplicates (same `kind` or `dirName` from a second file) become registry **issues**, not overrides.

Global kinds win precedence in practice: they load first, so a domain file reusing an existing `kind` name is rejected with a duplicate-kind issue.

<Warning>
Disabling a domain removes its kinds from the registry on the next CLI invocation. Existing artifact **files** remain on disk, but operations that need a registered kind (for example `artifact create --kind deal`) fail until you re-enable. `loopany doctor` may warn when artifacts reference kinds that are no longer loaded.
</Warning>

### Example domain kind (`deal`)

E2E tests scaffold a minimal CRM pack:

```markdown
---
kind: deal
dirName: deals
indexedFields: [status]
---
## Frontmatter
```yaml
title:  { type: string, required: true }
status: { type: enum, values: [open, won, lost] }
```
```

Workflow:

<Steps>
<Step title="Write the pack">
Create `$LOOPANY_HOME/domains/crm/kinds/deal.md` (and any sibling kind files).
</Step>
<Step title="Enable the domain">
```bash
loopany domain enable crm
```
</Step>
<Step title="Verify kinds">
```bash
loopany kind list
```
`deal` appears only after enable.
</Step>
<Step title="Create scoped artifacts">
```bash
loopany artifact create --kind deal --slug acme-q2 --title "Acme Q2" --status open --domain crm
```
</Step>
</Steps>

## Domain tag on artifacts

`domain` is a **built-in** store field accepted on every kind without appearing in each kind’s Frontmatter schema. Set at create time with `--domain`, or later with `artifact set <id> --field domain --value <name>`.

Core kinds such as `task`, `signal`, `brief`, `mission`, `learning`, `note`, and `person` also declare optional `domain` in their kind specs and often list `domain` in `indexedFields` where filtering matters.

The tag is organizational metadata:

- Does **not** move files into `domains/`
- Does **not** auto-enable a domain pack
- Can differ from pack name in theory, but convention is to align tag and pack (e.g. both `crm`)

## CLI commands

| Command | JSON shape | Behavior |
|---------|------------|----------|
| `loopany domain list` | `{ enabled, observed_only }` | `enabled` from config; `observed_only` = domain values seen on artifacts but not enabled |
| `loopany domain enable <name>` | `{ enabled }` | Appends name to `enabled_domains`, sorted |
| `loopany domain disable <name>` | `{ enabled }` | Removes name; no error if missing |

<RequestExample>
```bash
loopany domain list
```
</RequestExample>

<ResponseExample>
```json
{
  "enabled": ["crm"],
  "observed_only": ["ads"]
}
```
</ResponseExample>

`observed_only` helps you spot informal tagging before you enable packs — for example five tasks already carry `domain: sales` but `sales` is not in `enabled_domains`.

## Queries and filters

Commands that accept `--domain <name>` filter on the artifact frontmatter tag:

| Command | Filter semantics |
|---------|------------------|
| `artifact list --domain D` | Index `byDomain(D)` |
| `followups --due … --domain D` | Due items where `frontmatter.domain === D` |
| `search <query> --domain D` | Search index `domain` column |
| `refs <id> --domain D` | Keeps edges where **both** endpoints have `domain: D` |

`refs --domain` is stricter than list/search: cross-domain edges are dropped unless both artifacts share the tag.

## Doctor: domain coverage

`loopany doctor` includes a **domain coverage** check (warning, not fail). Any artifact with a `domain` string not present in `enabled_domains` is listed, e.g. `my-task: domain "unknown-d" not in enabled_domains`.

<Tip>
After you start tagging artifacts informally, run `domain list` and `doctor` together: enable packs you are ready to support, or remove stray tags.
</Tip>

## Factory UI

`loopany factory` builds a graph payload with:

- `domains` — distinct non-null `domain` values on nodes
- `enabledDomains` — copy of `config.enabled_domains`

The UI uses these for labeling; it does not read `domains/*/settings.yaml`.

## When to propose a domain vs a note

Default decision path lives in `skills/loopany-core/conventions/taxonomy.md` (skill: **new-concept**). Short version:

**Write a `note` first** when “I want to track X” and no existing kind fits — unless structure pays back (state machine, dedup identity, typed queries, fixed body sections).

**Propose a domain** only when usage shows a **separable scope** — a vertical (sales, ads), a rhythm (weekly review), or a workflow that wants its own kinds/settings/skills, and that does not belong in the global pool.

Practical signals from taxonomy:

- ≥ **5** artifacts already use the same informal `domain: <name>`, **or**
- A new kind would **only** make sense inside one scope

**Prefer domain over orphan domain kind:** if torn between “new kind globally” and “new domain that owns the kind”, choose the domain so vertical kinds stay in `domains/<name>/kinds/`.

| Situation | Recommendation |
|-----------|----------------|
| One-off preference (“client likes async standups”) | `note` |
| Published entity with identity + metrics (`post`, `customer`) | Domain kind inside e.g. `content` or `crm` |
| Cadence/workstream (“weekly investor update”) | Domain reusing core `brief` + `task`, not a new core kind |
| Vertical entity (`deal`, `order`) | `kind` in domain pack, not core `kinds/` |

Domain scaffolding after user agreement is still maturing in skills: surface the proposal, then create `domains/<name>/kinds/` and run `domain enable`.

### Anti-patterns

- Proposing a **core** kind for verticals (`deal`, `recipe`, `campaign`) — belongs in a domain pack.
- Creating a **domain** when an extra field on a `note` suffices.
- Using **`task`** to hold entities (tweets, customers) — use `note` or a domain kind; link with graph relations.

## Core kinds vs domain kinds

| | Core kinds (`kinds/`) | Domain kinds (`domains/<n>/kinds/`) |
|--|----------------------|-------------------------------------|
| Shipped | Bundled at `loopany init` from `skills/loopany-core/kinds` | Never pre-shipped; user/agent authored |
| Load rule | Always | Only when domain enabled |
| Purpose | Agent lifecycle slots (`task`, `signal`, `mission`, …) | Vertical entities (`deal`, `contact`, …) |

Promotion flow for a new domain kind mirrors global kinds: author `domains/<name>/kinds/<kind>.md`, enable domain, use `loopany artifact create --kind <kind> …`. Global kind proposal via `loopany kind propose` applies to workspace `kinds/`, not domain paths.

## Mental model: storage vs organization

```text
artifacts/          ← canonical pool (immutable write rules)
domains/<name>/       ← scope-local kind definitions (+ planned config)
config.yaml           ← which packs are active
frontmatter.domain    ← lightweight scope label on any artifact
```

An artifact can be tagged `domain: crm` while also referenced from graph edges to mission-wide artifacts with no domain tag. Multiple domains do not duplicate storage — they filter views and extend validation.

<Note>
`src/core/domain-loader.ts` is listed in project docs as a future module; loading is inline in `engine.ts` today. Do not expect manifest/settings files to affect CLI behavior until a later release wires them.
</Note>

## Verification checklist

<Steps>
<Step title="List domains">
`loopany domain list` — confirm `enabled` and any `observed_only` names.
</Step>
<Step title="Enable and list kinds">
`loopany domain enable <name>` then `loopany kind list` — pack kinds appear.
</Step>
<Step title="Create tagged artifact">
`loopany artifact create --kind task --title "..." --status todo --domain <name>` then `artifact list --domain <name>`.
</Step>
<Step title="Doctor">
`loopany doctor` — domain coverage should be `ok` when every used tag is enabled.
</Step>
</Steps>

## Related pages

<CardGroup>
<Card title="Kinds and validation" href="/kinds-and-validation">
Markdown kind definitions, dynamic schemas, and immutable artifact writes — including how domain packs register kinds.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`config.yaml` keys, `LOOPANY_HOME`, and schema version guards.
</Card>
<Card title="Artifact commands" href="/artifact-commands">
`--domain` on create, `artifact set`, and list filters.
</Card>
<Card title="CLI reference" href="/cli-reference">
Full `loopany domain` subcommand surface and JSON stdout conventions.
</Card>
<Card title="Doctor and troubleshooting" href="/doctor-and-troubleshooting">
Domain coverage warnings and disabled-pack edge cases.
</Card>
</CardGroup>

---

## 08. Skills library

> Agent-readable SKILL.md packs (resolver, core, capture, reflect, review), routing table, cross-skill chaining, and harness-neutral memory injection.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/08-skills-library.md
- Generated: 2026-06-05T18:58:48.693Z

### Source Files

- `skills/loopany-resolver/SKILL.md`
- `skills/loopany-core/SKILL.md`
- `skills/loopany-capture/SKILL.md`
- `skills/loopany-reflect/SKILL.md`
- `skills/loopany-review/SKILL.md`
- `injections/resolver-memory.md`
- `INSTALL_FOR_AGENTS.md`

---
title: "Skills library"
description: "Agent-readable SKILL.md packs (resolver, core, capture, reflect, review), routing table, cross-skill chaining, and harness-neutral memory injection."
---

The loopany CLI does not load or execute skills — judgment lives in markdown under `skills/` in the cloned repo (conventionally `~/loopany-src/skills/`), while `loopany init` copies only kind definitions from `skills/loopany-core/kinds/` into `$LOOPANY_HOME/kinds/`. Agents read `loopany-resolver` first, follow its routing table to the right pack, then call deterministic `loopany` commands documented in each skill.

## Architecture

```mermaid
flowchart TB
  subgraph harness["Agent harness"]
    MEM["Host memory file<br/>CLAUDE.md / AGENTS.md / /memory add"]
    AGT["Agent session"]
  end

  subgraph repo["~/loopany-src (repo)"]
    INJ["injections/resolver-memory.md"]
    RES["skills/loopany-resolver/SKILL.md"]
    CORE["skills/loopany-core/"]
    CAP["skills/loopany-capture/"]
    REF["skills/loopany-reflect/"]
    REV["skills/loopany-review/"]
    MIG["skills/migrations/"]
  end

  subgraph workspace["$LOOPANY_HOME (workspace)"]
    KINDS["kinds/*.md"]
    ART["artifacts/"]
    REFS["references.jsonl"]
  end

  subgraph cli["loopany CLI"]
    OPS["artifact / refs / trace / followups / doctor"]
  end

  INJ --> MEM
  MEM --> AGT
  AGT -->|"Read at task end"| RES
  RES --> CORE & CAP & REF & REV
  CORE --> OPS
  CAP --> OPS
  REF --> OPS
  REV --> OPS
  init["loopany init"] --> KINDS
  OPS --> ART & REFS
```

<Note>
Skills are **provider- and harness-neutral**: any agent that can read files from disk (or a catalog mirror of the repo) can follow the same packs. No API keys, hosted skill registry, or model-specific runtime is required — only a stable path to `loopany-resolver/SKILL.md` and the injection snippet.
</Note>

## Pack inventory

| Pack | Path | Role |
|------|------|------|
| **Resolver** | `skills/loopany-resolver/SKILL.md` | First read on any loopany interaction; bootstrap gate + routing table + chaining rules |
| **Core** | `skills/loopany-core/SKILL.md` | Artifact CRUD routing; kind playbooks under `kinds/`; conventions under `conventions/` |
| **Capture** | `skills/loopany-capture/SKILL.md` | Post-work capture: event→kind routing, quality gate, subagent dispatch |
| **Reflect** | `skills/loopany-reflect/SKILL.md` | Pattern discovery → `learning` / `skill-proposal`; accept/reject apply flow |
| **Review** | `skills/loopany-review/SKILL.md` | Parameterized daily / weekly / monthly reviews; frequency detail in `references/` |
| **Migrations** | `skills/migrations/v*-to-v*/SKILL.md` | Agent-executable schema migration narratives + standalone `scripts/` |

Each operational pack uses Anthropic Agent Skills frontmatter (`name`, `description` with explicit triggers). The runtime never parses these files; they are instructions for the agent only.

## Bootstrap gate

Before any skill-specific work, the resolver requires an active mission:

```bash
loopany artifact list --kind mission --status active
```

No active mission → run `ONBOARDING.md` and **stop**. No capture, reflect, or review skill fires without a mission-backed workspace.

## Resolver routing table

Match the trigger, read the target `SKILL.md`, then act. When two rows match, read both — they chain.

| Trigger | Target pack | Read |
|---------|-------------|------|
| Any artifact CRUD (`create` / `get` / `list` / `append` / `status` / `set`) | core | `skills/loopany-core/SKILL.md` |
| Substantive work just ended (PR shipped, incident resolved, decision made) | capture | `skills/loopany-capture/SKILL.md` |
| `reflect` / `what have we learned` / ≥3 tasks done / writing `learning` or `skill-proposal` | reflect | `skills/loopany-reflect/SKILL.md` |
| `accept <proposal-slug>` / `reject <proposal-slug>` / review proposals | reflect | `skills/loopany-reflect/SKILL.md` |
| `what's due today` / daily check-in / session start | review (daily) | `skills/loopany-review/SKILL.md` + `references/daily.md` |
| `what's slipping` / weekly check / workspace health | review (weekly) | `skills/loopany-review/SKILL.md` + `references/weekly.md` |
| Mission drift / structural questions / monthly cadence | review (monthly) | `skills/loopany-review/SKILL.md` + `references/monthly.md` |
| Choosing a relation verb / `loopany refs add` | core conventions | `skills/loopany-core/conventions/relations.md` |
| Note vs kind vs domain decision | core conventions | `skills/loopany-core/conventions/taxonomy.md` |

### Disambiguation rules

- **Most specific wins** — e.g. `create a task` → core, not capture.
- **Conventions stack** — core does not exempt `refs add` from the six canonical relation verbs.
- **When in doubt, ask** before committing an artifact.

## Core kind routing

After loading core, route to the kind playbook under `$LOOPANY_HOME/kinds/` (copied from `skills/loopany-core/kinds/` at init). Read the kind file — schema **and** `§ Playbook` — before create or modify.

| Trigger | Kind playbook |
|---------|---------------|
| Committing to work, `[change]` / `[incident]` | `kinds/task.md` |
| Writing `## Outcome`, flipping task status | `kinds/task.md § Playbook` |
| Noticed something, can't act now | `kinds/signal.md` |
| Dismissing / upgrading a signal | `kinds/signal.md § Playbook` |
| Writing a learning | `kinds/learning.md` |
| Writing a skill-proposal | `kinds/skill-proposal.md` |
| Mission create/update | `kinds/mission.md` |
| Brief / person / journal | respective kind file |
| Anything else | `kinds/note.md` |

Pass `--slug` whenever the artifact will be cited in `[[wiki-links]]` or graph edges. Omit only for batch ingestion.

## Cross-skill chaining

Some workflows intentionally span packs in sequence:

| Chain | Flow |
|-------|------|
| **Capture → Core** | Capture picks the kind; core's playbook governs creation |
| **Capture → Reflect** | After ≥3 captures in a session, suggest reflect |
| **Reflect → Core** | Reflect writes learnings/proposals via core playbooks |
| **Review → Core** | Review dispatches each surfaced item to its kind playbook |
| **Review → Reflect** | Weekly review with ≥3 resolutions → suggest reflect |

```text
Session end ──► resolver-memory (inject) ──► resolver ──► capture?
                                              │
                    ┌─────────────────────────┼─────────────────────────┐
                    ▼                         ▼                         ▼
                  core                    reflect                     review
                    │                         │                         │
                    └──────────── loopany CLI (artifacts + graph) ──────┘
```

Skill-to-skill references in markdown bodies use `[[<path>/SKILL.md]]` (path relative to the linking file, always ending in `SKILL.md`). Bare `[[slug]]` in artifact bodies is reserved for artifact IDs and becomes `mentions` edges — not skill links.

## Harness-neutral memory injection

The CLI has no skill loader. Persistent trigger discipline comes from appending one block into the host's memory primitive.

<Steps>
<Step title="Locate the canonical snippet">

`injections/resolver-memory.md` — instructs the agent to **Read** `~/loopany-src/skills/loopany-resolver/SKILL.md` at the end of every user-requested task, before the final reply.

</Step>
<Step title="Append idempotently (coding CLIs)">

<CodeGroup>
```bash title="Claude Code"
grep -q "loopany skill resolver" ~/.claude/CLAUDE.md 2>/dev/null || \
  cat ~/loopany-src/injections/resolver-memory.md >> ~/.claude/CLAUDE.md
```

```bash title="Codex / AGENTS.md"
TARGET="$HOME/AGENTS.md"
grep -q "loopany skill resolver" "$TARGET" 2>/dev/null || \
  cat ~/loopany-src/injections/resolver-memory.md >> "$TARGET"
```
</CodeGroup>

</Step>
<Step title="Register on agent platforms">

For hosts with a `/memory add` primitive, register the same file contents (collapse newlines if the platform rejects multiline values).

</Step>
<Step title="Verify without self-query">

```bash
grep -l "loopany skill resolver" ~/.claude/CLAUDE.md ~/AGENTS.md 2>/dev/null
test -f ~/loopany-src/skills/loopany-resolver/SKILL.md && echo "RESOLVER present"
```

Start a **fresh** session and ask where the resolver lives. A correct answer without file search means injection worked.

</Step>
</Steps>

<Info>
`git pull` on `~/loopany-src` updates skill **content** without re-running injection — the memory block only holds a pointer to the resolver path. A future `loopany doctor` check for resolver registration is planned but not implemented yet.
</Info>

## Cadence and recurring packs

| Cadence | Pack | Typical trigger |
|---------|------|-------------------|
| Daily | `loopany-review` | `loopany followups --due today`; session start |
| Weekly | `loopany-review` | overdue sweep + `loopany doctor` + parking lots |
| Weekly | `loopany-reflect` | fresh outcomes; pattern thresholds |
| Monthly | `loopany-review` | mission alignment + structural drift |

On coding CLIs (Claude Code, Codex), durable platform cron is unreliable — default to **session-boundary** prompts instead of silent cron registration. Agent platforms with durable cron may register jobs silently; record job IDs for re-install audits. See `INSTALL_FOR_AGENTS.md` Step 4 for the full decision tree.

## Skill evolution (never direct edits)

Agents **must not** edit files under `skills/` except through an accepted `skill-proposal` artifact:

1. Reflect writes `learning` (+ optional `skill-proposal` citing evidence).
2. User accepts or rejects via reflect's proposal-apply flow.
3. On accept: apply only the described change, append `## Outcome` to the proposal, `loopany artifact status … accepted`, git-commit target + proposal together.
4. `changeType: add` requires `## Skill draft` and `## Resolver entry` so new packs are reachable from the routing table.

Rejected proposals record reasons in `## Outcome` so reflect does not re-suggest the same rule.

## Conventions sub-packs

| File | Use when |
|------|----------|
| `conventions/relations.md` | `loopany refs add --relation` — six canonical verbs: `led-to`, `addresses`, `mentions`, `supersedes`, `follows-up`, `cites` |
| `conventions/taxonomy.md` | Choosing `note` vs new kind vs new domain (default: `note`) |

`relation` remains an open registry in code, but synonyms fragment `refs` / `trace` queries — conventions live in skills, not TypeScript (`src/core/references.ts`).

## Migrations as skills

Schema jumps ship under `skills/migrations/v<from>-to-v<to>/` with a readable `SKILL.md` and ordered `scripts/*.ts`. `loopany migrate` discovers migrations; the **agent** reads the skill and runs scripts — the CLI does not execute them. This keeps transforms separable from the thin harness.

## Verification

| Check | Command / artifact |
|-------|-------------------|
| Resolver on disk | `test -f ~/loopany-src/skills/loopany-resolver/SKILL.md` |
| Injection landed | `grep -l "loopany skill resolver" ~/.claude/CLAUDE.md …` |
| Workspace health | `loopany doctor` |
| End-to-end skill behavior | `./test/skill-regression.sh` (symlinks core/reflect/review/capture into a temp Claude skills dir; 10 scenarios via `claude -p`) |

<Warning>
Skill regression requires the `claude` CLI, an API key, and ~5 minutes for a full pass. It validates artifact routing and capture/review/reflect behavior — not resolver injection into user memory files.
</Warning>

## Anti-patterns (resolver-level)

- Acting without reading the resolver routing table first
- Skipping a pack's anti-patterns section (failure modes live there)
- Trusting session memory over re-reading skills (skills change via proposals)
- Writing artifacts without reading the target kind playbook
- Exposing loopany vocabulary (`kind`, `slug`, CLI flags) to end users during normal conversation — translate in user-facing text per `INSTALL_FOR_AGENTS.md`

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Clone, link CLI, inject resolver memory into your agent host.
</Card>
<Card title="Workspace setup" href="/workspace-setup">
`loopany init`, bundled kinds, onboarding, cadence, and `doctor`.
</Card>
<Card title="Capture workflow" href="/capture-workflow">
Event→kind routing, quality gate, and subagent dispatch.
</Card>
<Card title="Reflect workflow" href="/reflect-workflow">
Evidence gathering, pattern thresholds, learnings, and proposals.
</Card>
<Card title="Periodic review" href="/periodic-review">
Daily, weekly, and monthly review scopes and closure gate.
</Card>
<Card title="Self-improvement loop" href="/self-improvement-loop">
Task outcomes → reflect → accept/reject → git-backed skill diffs.
</Card>
<Card title="Kinds and validation" href="/kinds-and-validation">
Workspace kind files, Zod schemas, and immutable write rules.
</Card>
</CardGroup>

---

## 09. Self-improvement loop

> Task ## Outcome evidence, learning beliefs, skill-proposal accept/reject flow, checkAt followups, and the rule that agents never edit skills directly.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/09-self-improvement-loop.md
- Generated: 2026-06-05T18:59:25.759Z

### Source Files

- `skills/loopany-reflect/SKILL.md`
- `skills/loopany-core/kinds/learning.md`
- `skills/loopany-core/kinds/skill-proposal.md`
- `skills/loopany-core/kinds/task.md`
- `CLAUDE.md`
- `src/commands/followups.ts`

---
title: "Self-improvement loop"
description: "Task ## Outcome evidence, learning beliefs, skill-proposal accept/reject flow, checkAt followups, and the rule that agents never edit skills directly."
---

loopany’s self-improvement loop is implemented as markdown artifacts plus skills (`loopany-capture`, `loopany-reflect`, `loopany-review`): completed `task` records supply `## Outcome` evidence, `loopany-reflect` distills `learning` beliefs and optional `skill-proposal` artifacts, humans accept or reject proposals before any `SKILL.md` edit is applied and committed, and `checkAt` on learnings or accepted proposals drives `loopany followups` closure via daily review.

## Components

| Piece | Kind / command | Role |
|-------|----------------|------|
| Evidence | `task` (`done` / `failed`) | Immutable `## Outcome` body; substrate for reflect |
| Belief | `learning` (`active` → `superseded` / `archived`) | Declarative hypothesis with `evidence[]` |
| Behavior change | `skill-proposal` (`pending` → `accepted` / `rejected`) | Contract for a skill edit; only path to mutate skills |
| Discovery | `skills/loopany-reflect/SKILL.md` | Pattern thresholds, write learnings/proposals, apply accept/reject |
| Scheduling | `checkAt` + `loopany followups` | Due-item query; daily review closes the loop |
| Capture gate | `skills/loopany-capture/SKILL.md` | End-of-work routing; refuses weak outcomes |

Kinds are defined under `skills/loopany-core/kinds/` and copied into `$LOOPANY_HOME/kinds/` at init. Runtime parses them into Zod frontmatter validators and status machines (`src/core/kind-registry.ts`); the CLI enforces **status transitions**, not body-section presence (section rules are kind contracts enforced by agent skills).

## End-to-end flow

```mermaid
sequenceDiagram
  participant Work as Agent work
  participant Cap as loopany-capture
  participant Store as artifacts/*.md
  participant Ref as loopany-reflect
  participant User as Human
  participant Skill as skills/*/SKILL.md
  participant Git as git

  Work->>Cap: substantive work ends
  Cap->>Store: append ## Outcome; status done|failed
  Ref->>Store: list done tasks, dismissed signals
  Ref->>Store: create learning (+ optional skill-proposal)
  User->>Ref: accept|reject proposal
  alt accepted
    Ref->>Skill: apply ## Proposed change only
    Ref->>Store: append ## Outcome; status accepted
    Ref->>Git: commit skill + proposal
  else rejected
    Ref->>Store: append ## Outcome; status rejected
  end
  Store->>Store: checkAt due
  Note over Store: loopany followups + loopany-review daily
```

<Note>
Reflect looks **forward** (new patterns). `loopany-review` daily revalidates **due** `checkAt` on existing learnings and proposals — do not mix scopes.
</Note>

## Task outcomes as evidence

Every terminal `task` must carry a `## Outcome` section before `done` or `failed` (kind contract in `skills/loopany-core/kinds/task.md`). The section answers what shipped or failed, whether observable evidence moved, and what you would do differently. Optional `## Before` is strongly recommended for metric work so reflect can falsify beliefs.

Terminal transition pattern:

```bash
loopany artifact append <task-slug> --section Outcome --content "..."
loopany artifact status <task-slug> done --reason "<one line>"
```

`loopany-capture` is the quality gate at work boundaries: skip capture when you cannot write a one-sentence outcome, when the event is internal tooling noise, or when `loopany artifact list --contains "<phrase>"` finds a duplicate. Beliefs from fewer than two data points route to reflect, not ad-hoc `learning` creation during capture.

<Warning>
`artifact status` validates the kind **status machine** only (`src/core/artifact-store.ts` `setStatus`). It does not block `done` without `## Outcome` — agents must append Outcome first or pollute the reflect substrate.
</Warning>

## Reflect: evidence → pattern → artifacts

`skills/loopany-reflect/SKILL.md` runs in two modes: **reflect** (discover) and **proposal-apply** (accept/reject).

### When to reflect

- User: “reflect”, “what have we learned”, “improve yourself”
- Weekly cadence (paired with `loopany-review`; monthly structural review stays in review)
- After ≥ 3 tasks reach `done` in a short window

Do **not** reflect after every single task.

### Gather and dedupe

```bash
loopany artifact list --kind task --status done
loopany artifact list --kind signal
loopany artifact list --kind signal --status dismissed
loopany artifact list --kind learning --status active
loopany artifact list --kind skill-proposal --status rejected
```

Filter to a recent window (~1 week, `createdAt` newest first). Subtract artifact IDs already listed in `evidence` on active learnings and non-rejected proposals so reflect does not duplicate work.

### Pattern thresholds

| Pattern | Threshold |
|---------|-----------|
| Same class of outcome | ≥ 3 tasks |
| Belief refuted | ≥ 2 tasks contradicting an active learning |
| Belief needs caveat | ≥ 2 tasks |
| Dismissed signal keeps recurring | ≥ 3 dismissals over ≥ 2 weeks |

Below threshold: report insufficient evidence (skill regression scenario 8 expects **no** new `lrn-*` files). Do not create a `learning` from one bad outcome or re-propose a `rejected` skill-proposal.

## Learning artifacts

`learning` stores a **belief**, not a behavior change. Title is the belief sentence. Frontmatter (camelCase since v0.2):

| Field | Notes |
|-------|-------|
| `title` | Required — declarative belief |
| `evidence` | String array; ≥ 2 artifact slugs at creation time |
| `checkAt` | Date; 1–3 months; concrete revalidation question in body |
| `supersedes` | Slug of prior learning when belief shifts |
| `status` | `active` (default) → `superseded` → `archived` |
| `mentions` | Optional structural links |

Required body sections at creation: `## Observation`, `## Evidence`, `## Scope`, `## Check-at`.

Create example:

```bash
loopany artifact create --kind learning \
  --slug short-attention-spans-2026 \
  --title "Deals with more than three stakeholders close 2.5x slower" \
  --evidence "task-a,task-b,task-c" \
  --mentions "fundraising-2027" \
  --check-at 2026-07-22 \
  --content "$(cat <<'EOF'
## Observation
...

## Evidence
- task-a — "..."
- task-b — "..."

## Scope
...

## Check-at
...
EOF
)"
```

When understanding changes, **do not edit** the old learning. Create a new learning, link `loopany refs add --from <new> --to <old> --relation supersedes`, and `loopany artifact status <old> superseded --reason "superseded by <new>"`. Terminal `superseded` / `archived` require `## Outcome` on the learning kind.

Many learnings stop at “now we know” with no skill change.

```mermaid
stateDiagram-v2
  [*] --> active
  active --> superseded: belief revised
  active --> archived
  superseded --> archived
```

## Skill-proposal artifacts

`skill-proposal` is the **only** path from agent judgment to skill file changes (`skills/loopany-core/kinds/skill-proposal.md`, `CLAUDE.md` architectural constraint). Agents must not edit `SKILL.md` files directly.

| Field | Notes |
|-------|-------|
| `targetSkill` | Path to `SKILL.md` (existing for `modify`/`remove`; planned path for `add`) |
| `changeType` | `modify` (default), `add`, `remove` |
| `status` | `pending` → `accepted` \| `rejected` |
| `evidence` | Supporting artifact slugs |
| `checkAt` | On accept: schedule “did this help?” review |

Required body before pending review: `## Motivation` (cite learning), `## Proposed change`, `## Expected effect`, `## Check-at`. For `changeType: add`, also `## Skill draft` (full new `SKILL.md`) and `## Resolver entry` (row for `skills/loopany-resolver/SKILL.md`).

After accept or reject, body must include `## Outcome` before the status flip.

```mermaid
stateDiagram-v2
  [*] --> pending
  pending --> accepted: human accept + skill edit + git commit
  pending --> rejected: human reject + Outcome reason
```

Verify the chain:

```bash
loopany trace <proposal-slug> --direction backward
loopany refs <proposal-slug> --direction out --relation mentions
```

## Accept and reject flows

Proposal-apply mode (`loopany-reflect` Part 2):

```bash
loopany artifact list --kind skill-proposal --status pending
loopany artifact get <proposal-slug>
```

<Steps>
  <Step title="Accept">
    Read proposal, cited learning (`refs` / `mentions`), and current `targetSkill` file. Apply **only** the described change. Append `## Outcome` to the proposal (literal diff summary). Run `loopany artifact status <proposal-slug> accepted --reason "..."`. Git-commit the skill file and proposal artifact together.
  </Step>
  <Step title="Reject">
    Read proposal. Append `## Outcome` with a clear reason (future reflect reads this). `loopany artifact status <proposal-slug> rejected --reason "..."`.
  </Step>
</Steps>

Edge cases from the reflect skill: reject if target file missing, cited learning was superseded, or multiple pending proposals touch the same file (accept one at a time, re-read target between).

<Tip>
Resolver routing for proposals: `skills/loopany-resolver/SKILL.md` maps “accept &lt;proposal-slug&gt;” / “reject &lt;proposal-slug&gt;” to `loopany-reflect`.
</Tip>

## checkAt and followups

`checkAt` is indexed frontmatter on `task`, `learning`, and other kinds that declare it. Set only when there is a concrete future question; missing dates are better than ignored ones.

```bash
loopany followups --due today
loopany followups --due overdue
loopany followups --due next-7d
loopany followups --due today --include-done true
```

Implementation (`src/commands/followups.ts`, `src/core/index.ts` `followups()`):

- Returns artifacts whose `checkAt` date is on or before the cutoff.
- **Default:** hides artifacts in a **terminal** status (no outgoing transitions in the kind status machine — e.g. `task` `done`, `skill-proposal` `accepted`).
- `overdue` further restricts to dates strictly before today.

Daily review (`skills/loopany-review/references/daily.md`) runs `loopany followups --due today` only (not overdue — weekly’s job). For each due learning, `loopany trace <learning-slug> --direction backward` informs revalidation. Closure gate: every surfaced item ends resolved, deferred (`checkAt` pushed with reason), or retired (`checkAt` removed with note).

Weekly review nudges pending proposals (`artifact list --kind skill-proposal --status pending`; >5 pending → nudge) and suggests reflect when ≥3 resolutions occurred in the pass.

## Cadence (skills, not CLI cron)

| Cadence | Skill | Self-improvement role |
|---------|-------|------------------------|
| End of substantive work | `loopany-capture` | Write/refine task outcomes |
| Weekly | `loopany-reflect` | New learnings + proposals |
| Daily | `loopany-review` | Close due `checkAt` items |
| Weekly | `loopany-review` | Overdue sweep, doctor, feed reflect |
| On demand | `loopany-reflect` proposal-apply | Accept/reject pending proposals |

Coding CLI hosts often use session-boundary prompts instead of durable cron (`INSTALL_FOR_AGENTS.md`); judgment stays in skills, queries stay in `loopany` CLI.

## Quick reference

```text
CAPTURE:  work ends → quality gate → append Outcome → task done|failed
REFLECT:  list evidence → pattern? → learning → (optional) skill-proposal pending
ACCEPT:   read spr → read lrn → edit targetSkill → Outcome → accepted → git commit
REJECT:   read spr → Outcome (reason) → rejected
FOLLOWUP: checkAt due → followups → review classify → resolve|defer|retire
INVARIANT: never edit skills except via accepted skill-proposal
```

<Check>
Self-improvement needs no separate “lesson” entity: outcomes, learnings, proposals, graph edges (`supersedes`, `mentions`, `cites`), and git-backed skill files are the full loop.
</Check>

## Related pages

<CardGroup>
  <Card title="Reflect workflow" href="/reflect-workflow">
    Step-by-step reflect and proposal-apply procedures.
  </Card>
  <Card title="Capture workflow" href="/capture-workflow">
    Event routing and the Outcome quality gate at task close.
  </Card>
  <Card title="Periodic review" href="/periodic-review">
    Daily followups, weekly doctor/overdue, monthly mission checks.
  </Card>
  <Card title="Self-improvement example" href="/self-improvement-example">
    Recipe: three done tasks → reflect → accept proposal → git diff.
  </Card>
  <Card title="Kinds and validation" href="/kinds-and-validation">
    Kind markdown, status machines, and frontmatter validators.
  </Card>
  <Card title="Skills library" href="/skills-library">
    Resolver, core, capture, reflect, and review skill packs.
  </Card>
</CardGroup>

---

## 10. Workspace setup

> Run loopany init, verify bundled kinds copied, complete ONBOARDING.md once, register cadence (cron vs session-boundary), and confirm doctor passes.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/10-workspace-setup.md
- Generated: 2026-06-05T19:00:21.263Z

### Source Files

- `src/commands/init.ts`
- `ONBOARDING.md`
- `INSTALL_FOR_AGENTS.md`
- `src/commands/doctor.ts`
- `skills/loopany-core/kinds/mission.md`
- `skills/loopany-core/kinds/person.md`
- `skills/loopany-resolver/SKILL.md`

---
title: "Workspace setup"
description: "Run loopany init, verify bundled kinds copied, complete ONBOARDING.md once, register cadence (cron vs session-boundary), and confirm doctor passes."
---

`loopany init` scaffolds the per-user brain at `$LOOPANY_HOME` (default `~/loopany`): directories, `config.yaml` with the current `schemaVersion`, and a one-time copy of bundled kind definitions from `skills/loopany-core/kinds/`. A healthy workspace still needs a single onboarding pass (`ONBOARDING.md`), an agent-chosen cadence for review/reflect skills, and `loopany doctor` exit `0` before routine capture and graph work.

## Prerequisites

| Requirement | Notes |
|-------------|--------|
| Bun | Runtime for the CLI (`bun install && bun link` from the repo checkout) |
| `loopany` on `PATH` | Typically `~/.bun/bin` after `bun link` |
| Writable brain directory | Default `~/loopany`; override with `LOOPANY_HOME` |

<Note>
`LOOPANY_HOME` is read on every command via `getWorkspaceRoot()` — the workspace is per user, not per shell cwd.
</Note>

## Workspace layout after init

`runInit()` creates the root, `kinds/`, and `artifacts/`, writes `config.yaml` if missing, and copies any bundled `*.md` kind files not already present. It does **not** create `references.jsonl` or seed artifacts; those appear on first `refs add` / `artifact create`.

```text
$LOOPANY_HOME/                    # default ~/loopany
├── config.yaml                   # schemaVersion: <SCHEMA_VERSION> (currently 0.2.0)
├── kinds/                        # copied from repo skills/loopany-core/kinds/
│   ├── brief.md, journal.md, learning.md, mission.md, note.md
│   ├── person.md, signal.md, skill-proposal.md, task.md
├── artifacts/                    # empty until onboarding / capture
├── audit.jsonl                   # first line written on first CLI op (e.g. init)
└── references.jsonl              # created when first edge is added
```

## Initialize the workspace

<Steps>
<Step title="Run init">

```bash
loopany init
```

With a custom root:

```bash
LOOPANY_HOME=/path/to/brain loopany init
```

</Step>

<Step title="Confirm CLI output">

| Outcome | stdout signal |
|---------|----------------|
| First run | `Initialized loopany workspace at …` and `Created N file(s).` |
| Idempotent re-run | `Workspace already initialized — nothing to do.` |
| Needs onboarding | Blank line, then `NEXT — read ONBOARDING.md…` |

Init is idempotent: existing files (including kind copies) are left untouched; only missing pieces are added.

</Step>

<Step title="Verify bundled kinds">

```bash
ls "$LOOPANY_HOME/kinds"
loopany kind list
```

E2E expects exactly nine files under `kinds/`:

`brief.md`, `journal.md`, `learning.md`, `mission.md`, `note.md`, `person.md`, `signal.md`, `skill-proposal.md`, `task.md`.

`loopany kind list` prints JSON with one entry per loaded kind — use it when you need parsed schemas, not just filenames.

</Step>
</Steps>

### Init implementation constraints

- **Source of kind defs:** `skills/loopany-core/kinds/` in the repo checkout (not TypeScript enums).
- **Mission probe:** `needsOnboarding` is true when `artifacts/missions/` has no `*.md` files (any mission file suppresses the nag, not only `active`).
- **Audit:** `init` writes an `audit.jsonl` entry (`op: init`, ISO `ts`).

## Onboarding (run once)

`ONBOARDING.md` is the agent script for the first conversation. Rules that affect workspace health:

| Rule | Behavior |
|------|----------|
| Run at most once | If a `mission` with `status: active` already exists, exit immediately |
| Pre-read | `loopany --help` + skim `$LOOPANY_HOME/kinds/*.md` before speaking |
| Phase 3 artifacts | Person for the user + one or more `mission` artifacts with `status: active` and `hypothesis` set |
| Backfill | Optional silent capture with `[backfill]` prefix per `skills/loopany-capture/SKILL.md` |

### Artifacts doctor expects

`loopany doctor` onboarding check requires:

1. **Person id `self`** — `idx.byId('self')` must exist (v0.2 slug-as-id). Create with:

   ```bash
   loopany artifact create --kind person --slug self --name "Your Name"
   ```

   <Warning>
   `ONBOARDING.md` Phase 3 still says `prs-self`; that was the v0.1 id. On v0.2 workspaces, use `--slug self` or doctor fails with `self person artifact missing`.
   </Warning>

2. **≥1 active mission** — e.g.:

   ```bash
   loopany artifact create --kind mission --slug ship-v1 \
     --title "Ship loopany v1" --status active
   ```

After onboarding, `loopany init` no longer prints the `ONBOARDING` hint (any mission file under `artifacts/missions/` is enough).

### Resolver gate

`skills/loopany-resolver/SKILL.md` blocks all other skills until bootstrap passes:

```bash
loopany artifact list --kind mission --status active
```

No active mission → read `ONBOARDING.md` and stop.

## Cadence: cron vs session-boundary

Recurring work is defined in skills, not in `loopany init`. `INSTALL_FOR_AGENTS.md` Step 4 assigns **how** those skills fire — without asking the user to choose cron mechanics.

| Cadence | Skill | Role |
|---------|-------|------|
| Daily | `skills/loopany-review/SKILL.md` | `loopany followups --due today`; close items with state transitions |
| Weekly | `skills/loopany-review/SKILL.md` | Overdue sweep + `loopany doctor` + parking-lot queries |
| Weekly | `skills/loopany-reflect/SKILL.md` | Learnings + skill-proposals from recent outcomes |
| Monthly | `skills/loopany-review/SKILL.md` | Mission-drift and structural-drift detection |

```mermaid
flowchart LR
  subgraph durable ["§A Durable cron hosts"]
    CRON[Platform cron]
    CRON --> REV[loopany-review]
    CRON --> REF[loopany-reflect]
  end
  subgraph session ["§B Coding CLIs default"]
    START[Session start]
    START --> FU["followups --due today"]
    END[Week / month boundary]
    END --> SWEEP[Weekly review + reflect suggest]
  end
```

<Tabs>
<Tab title="§A — Durable cron (Hermes, OpenClaw, …)">

Register each skill on its schedule using the host’s cron mechanism. Run inside an agent session (judgment-heavy), not as bare shell-only cron unless you build a wrapper task.

Record job IDs / config paths for re-install audits. Do not describe registration to the user — only the plain-English cadence line below.

</Tab>
<Tab title="§B — Coding CLIs (default)">

Do **not** register platform cron on Claude Code–class hosts: recurring tasks expire (~7 days on Claude Code), so jobs die silently.

**Default:** prompt at session boundaries — start of day → `loopany followups --due today`; end of week → propose weekly review + reflect; month-end → mission review.

System-level `launchd` / crontab is optional future work captured as a `task` artifact if the user asks; not an onboarding question.

</Tab>
</Tabs>

### User-facing cadence line

End onboarding with one sentence (from `INSTALL_FOR_AGENTS.md`):

> I'll check in at the start of each day on what's due, propose a sweep + reflect at week's end, and surface mission-drift around month's end. You can run `loopany factory` anytime for a visual map of what's in here.

No registration menus or host-quality discussion during setup.

## Resolver injection (agent hosts)

Workspace files alone do not load skills. Append `injections/resolver-memory.md` into the host memory file so every task ends with a read of `skills/loopany-resolver/SKILL.md`:

```bash
grep -q "loopany skill resolver" ~/.claude/CLAUDE.md 2>/dev/null || \
  cat ~/loopany-src/injections/resolver-memory.md >> ~/.claude/CLAUDE.md
```

<Info>
`loopany doctor` does not yet verify resolver registration (noted as roadmap in `INSTALL_FOR_AGENTS.md`). Verify with `grep -l "loopany skill resolver"` on the target memory file.
</Info>

## Verify with doctor

```bash
loopany doctor
# machine-readable:
loopany doctor --format json
```

| Exit code | Meaning |
|-----------|---------|
| `0` | No check with `status: fail` |
| `1` | At least one failed check (stdout still prints the report) |

Doctor runs `bootstrap({ skipVersionCheck: true })` so it can **report** schema mismatch instead of throwing `SchemaVersionMismatchError`.

### Check inventory

| Check | Fail? | What it validates |
|-------|-------|-------------------|
| `workspace` | — | Always ok if doctor ran (path echoed) |
| `schema version` | fail | `config.yaml` `schemaVersion` vs binary `SCHEMA_VERSION` |
| `kinds` | fail | Every kind file parses; lists loaded kinds |
| `artifacts` | fail | All indexed frontmatter passes per-kind Zod |
| `references` | fail | No dangling `from` / `to` in `references.jsonl` |
| `onboarding` | fail | `self` person + ≥1 `mission` with `status: active` |
| `mission coverage` | warn | Tasks without a mission in `mentions` |
| `domain coverage` | warn | Artifact `domain` not in `enabled_domains` |

Fresh init → doctor fails onboarding (`self` / active mission missing). Minimal green path (matches e2e):

```bash
loopany artifact create --kind person --slug self --name "Test User"
loopany artifact create --kind mission --slug g1 --title "Test mission" --status active
loopany doctor
```

### Schema mismatch before doctor is green

If `schema version` fails, run the migration named in `problems`, e.g. `loopany migrate v0.1.0-to-v0.2.0`, following `skills/migrations/v0.1.0-to-v0.2.0/SKILL.md`. Do not hand-edit `schemaVersion` alone.

## Setup flow (end-to-end)

```text
install CLI (bun link)
    → loopany init
    → verify kinds/ + kind list
    → ONBOARDING.md (once) → self + active mission(s)
    → pick cadence §A or §B silently
    → optional resolver-memory injection
    → loopany doctor (exit 0)
    → resolver bootstrap OK → routine capture / review
```

## Troubleshooting

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| `No loopany workspace at … Run loopany init` | Missing `kinds/` under `$LOOPANY_HOME` | `loopany init` |
| Init keeps printing `ONBOARDING` | No `artifacts/missions/*.md` | Complete onboarding |
| Doctor: `self person artifact missing` | No person id `self` | `artifact create --kind person --slug self …` |
| Doctor: `no active mission` | Missions missing or none `active` | Create mission with `--status active` |
| Kind list empty / errors | Broken files in `kinds/` | Compare to bundled set; fix YAML in kind frontmatter |
| Doctor schema fail | Workspace older than binary | `loopany migrate …` per doctor `problems` |
| Cron “stopped working” on coding CLI | Session-scoped cron expiry | Switch to session-boundary prompts (§B) |

<Warning>
Warnings (`mission coverage`, `domain coverage`) do not change the exit code. Treat fails (`onboarding`, `references`, `schema version`, etc.) as blocking.
</Warning>

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Clone, Bun, `bun link`, `PATH`, and resolver injection into Claude Code / Codex / other hosts.
</Card>
<Card title="Quickstart" href="/quickstart">
First session after setup: mission, task, reference edge, optional factory.
</Card>
<Card title="Artifacts and workspace" href="/artifacts-and-workspace">
On-disk layout, slug-as-id rules, and `config.yaml` fields.
</Card>
<Card title="Doctor and troubleshooting" href="/doctor-and-troubleshooting">
Per-check failures, dangling refs, and recovery commands.
</Card>
<Card title="Periodic review" href="/periodic-review">
Daily followups, weekly doctor sweep, monthly mission drift.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`LOOPANY_HOME`, `LOOPANY_SKIP_VERSION_CHECK`, `schemaVersion`, `enabled_domains`.
</Card>
</CardGroup>

---

## 11. Capture workflow

> End-of-task capture quality gate, event→kind routing (task/signal/note), subagent dispatch pattern, and duplicate detection via artifact list --contains.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/11-capture-workflow.md
- Generated: 2026-06-05T18:59:39.908Z

### Source Files

- `skills/loopany-capture/SKILL.md`
- `skills/loopany-core/kinds/task.md`
- `skills/loopany-core/kinds/signal.md`
- `src/commands/artifact-list.ts`
- `src/commands/artifact-create.ts`
- `skills/loopany-resolver/SKILL.md`

---
title: "Capture workflow"
description: "End-of-task capture quality gate, event→kind routing (task/signal/note), subagent dispatch pattern, and duplicate detection via artifact list --contains."
---

`skills/loopany-capture/SKILL.md` defines when an agent records completed work into `$LOOPANY_HOME`. The resolver routes here after substantive work ends; capture picks the artifact kind, runs a quality gate, deduplicates with `loopany artifact list --contains`, and delegates writes to a subagent when the host supports it. Creation and status transitions still go through `loopany-core` kind playbooks and the CLI.

## Placement in the skill stack

```text
  User request completes (substantive boundary)
           │
           ▼
  loopany-resolver  ──trigger: PR shipped, incident resolved, decision made──►
           │
           ▼
  loopany-capture     quality gate → kind choice → duplicate check
           │
           ├──► loopany-core (artifact create / append / status / refs)
           │
           └──► loopany-reflect (suggest after ≥3 captures in one session)
```

<Info>
Read `skills/loopany-resolver/SKILL.md` first in any loopany session. Capture does not replace core: it decides *whether* and *what kind*; core’s kind playbooks govern *how* to write the artifact.
</Info>

Bootstrap still applies before capture: an active `mission` must exist (`loopany artifact list --kind mission --status active`). Without one, onboarding (`ONBOARDING.md`) blocks other skills.

## When capture runs

Capture is **end-of-task**, not per message.

| Run capture | Skip capture |
|-------------|--------------|
| PR shipped or merged | Typo fix, whitespace, one-liner |
| Incident resolved | Internal agent work (grep, read files, answer questions) |
| Decision with rationale worth finding later | Feelings or opinions without observable evidence |
| Problem noticed but deferred | Work still in progress (no `## Outcome` yet) |
| User says “record this” / “log this outcome” | Speculation (at most a concrete `signal`) |

**Substantive** means the session produced evidence or a decision a future reader would need—not routine exploration.

## Event → kind routing

| Event | Target kind | Playbook |
|-------|-------------|----------|
| PR shipped / merged | `task` (typically `--status done`) | `skills/loopany-core/kinds/task.md` |
| Incident resolved | `task` | same |
| Problem noticed, not acting now | `signal` | `skills/loopany-core/kinds/signal.md` |
| Outcome for an existing open task | append `## Outcome`, then terminal status | `task.md` § Playbook |
| Decision with rationale | `note` or closed `task` | flowchart below |
| Belief from ≥ 2 data points | `learning` | via `loopany-reflect`, not inline capture |

### Decision branches

```mermaid
flowchart TD
  A[Work concluded] --> B{Resolved a pending signal?}
  B -->|yes| C["task --status done + addressed edge"]
  B -->|no| D{Produced something shippable?}
  D -->|yes| E["task --status done with ## Outcome"]
  D -->|no| F{Reasoned preference / principle?}
  F -->|yes| G["note: decided: X over Y because Z"]
  F -->|no| H{Belief from ≥2 data points?}
  H -->|yes| I[learning via loopany-reflect]
  H -->|no| J[Default: note or skip quality gate]
```

<Note>
When unsure between `note` and a structured kind, default to `note` (`skills/loopany-capture/SKILL.md`). Use `task` only when you can write a one-sentence `## Outcome`.
</Note>

Core’s parallel routing table (`skills/loopany-core/SKILL.md`) aligns: acting now → `task`; deferred → `signal`; pattern beliefs → `learning` through reflect; duplicate → append, don’t fork.

## Quality gate

Apply **before** any `artifact create`. Skip when any gate fails:

| Gate | Rule |
|------|------|
| Observable | Must be fact-backed (path, session, commit, metric)—not mood |
| Already captured | `loopany artifact list --contains "<phrase>"` → append to match |
| Too small | Trivial edits don’t belong in the brain |
| Speculation | No concrete signal; weak “might be” items stay out |
| Internal work | Tooling and Q&A are not capture events |

**Outcome test:** if you cannot draft a one-sentence `## Outcome`, do not create a closing `task`.

`test/skill-regression.sh` scenario 4 encodes this: a README typo fix must not increase artifact count after the gate. Scenario 5 expects a shipped PR (rate limiting, metrics) to become a `task`.

<Warning>
Skipping the gate pollutes the workspace and downstream reflect. Noisy artifacts dilute pattern detection.
</Warning>

## Duplicate detection with `--contains`

Dedup is a **search-then-append** step, not a separate command.

<ParamField body="--contains" type="string">
Case-insensitive substring filter on `loopany artifact list`. Applied **after** `--kind`, `--status`, `--domain`, and other field filters narrow the candidate set.
</ParamField>

**Search order** (`src/commands/artifact-list.ts`):

1. Seed candidates from indexes (`byKind`, `byStatus`, `byDomain`, or `all`).
2. Apply frontmatter field filters (indexed fields use O(1) intersection when `--kind` is set).
3. For each remaining row: match string/`string[]` frontmatter values, else read body and match substring.

**Recommended queries** (kind playbooks):

```bash
# Capture skill — broad dedup before create
loopany artifact list --contains "<key phrase>"

# Task duplicates (prefer kind-scoped)
loopany artifact list --kind task --contains "<key phrase>"

# Signal duplicates
loopany artifact list --kind signal --contains "<symptom or path>"
```

| Match found | Action |
|-------------|--------|
| Same topic, open `task` in `todo`/`running` | Append evidence or `loopany refs add … --relation follows-up` |
| Same signal symptom | `loopany artifact append <sig-id> --section Recurrences` |
| Near-duplicate `person` | Check aliases via `--kind person --contains "<name>"` |
| No match | `loopany artifact create` per kind playbook |

E2E tests verify body match (`retention`), combined filters (`--kind task --status todo --contains retention`), title-only frontmatter (`orbit`), and `string[]` aliases (`stargaz` → `alice-chen`).

## Subagent dispatch pattern

Long sessions should **not** capture inline—the main agent loses focus and writes weaker artifacts.

```mermaid
sequenceDiagram
  participant Main as Main session
  participant Gate as Quality gate
  participant Sub as Subagent (optional)
  participant CLI as loopany CLI
  participant Core as loopany-core playbooks

  Main->>Gate: Work boundary reached
  Gate->>Gate: Observable? Already captured? Outcome test?
  alt Skip
    Gate-->>Main: No artifact
  else Capture
    Main->>Main: Compose 3–5 sentence context
    Main->>Sub: Record via loopany; read loopany-core for kind
    Sub->>Core: Read kind playbook
    Sub->>CLI: artifact create / append / status
    CLI-->>Sub: id, path
    Sub-->>Main: artifact ID
    Main->>Main: Continue primary task
  end
```

<Steps>
<Step title="Filter in the main session">
Run the quality gate and `--contains` dedup. Decide kind from the event table.
</Step>
<Step title="Compose context">
Write 3–5 sentences: what happened, why it matters, numbers, and any artifact IDs in scope.
</Step>
<Step title="Dispatch">
Prompt: “Record this via loopany. Read `loopany-core` to pick the right kind. Return the artifact ID.”
</Step>
<Step title="Subagent writes">
Subagent runs CLI commands from the kind playbook (create, append `Outcome`, `status`, `refs add`).
</Step>
<Step title="Resume">
Main session stores the returned ID; no context switch for markdown authoring.
</Step>
</Steps>

<Tip>
If the host has no subagent primitive, capture inline but keep commands minimal—one create or one append, not a long authoring detour.
</Tip>

## Typical CLI sequences

### Shipped work → `task`

```bash
loopany artifact list --kind task --contains "rate limiting"

loopany artifact create --kind task \
  --title "Ship API rate limiting (token bucket, Redis)" \
  --status done \
  --content "## Outcome
Added 100 req/min per user; staging 429 errors down ~80%."

# Or two-step if body grows:
loopany artifact create --kind task --title "..." --status running --content "## Plan ..."
loopany artifact append <id> --section Outcome --content "..."
loopany artifact status <id> done --reason "Merged PR #42"
```

Terminal `task` statuses (`done`, `failed`) require a `## Outcome` section in the kind playbook; the CLI enforces status **transitions**, not body shape—agents must append Outcome before flipping status.

### Deferred problem → `signal`

```bash
loopany artifact list --kind signal --contains "PayloadTooLargeError"

loopany artifact create --kind signal \
  --title "upload_image base64-encodes video → >5MB → PayloadTooLargeError" \
  --content "Observed in staging. **Risk:** large uploads fail."
```

Signals need a concrete observable title (≤ 160 chars). Close with `addressed` + graph edge, or `dismissed` + reason.

### Decision → `note`

```bash
loopany artifact create --kind note \
  --slug "redis-over-memcached-sessions" \
  --title "decided: Redis over Memcached for sessions because TTL is built-in" \
  --content "Evaluated both; Redis ops already in stack."
```

Pass `--slug` when the artifact will be cited in `[[wiki-links]]`.

### Signal resolved by new task

```bash
loopany artifact create --kind task --title "Fix upload size path" --status todo
# … work …
loopany artifact append <tsk-id> --section Outcome --content "..."
loopany artifact status <tsk-id> done --reason "Deployed fix"
loopany artifact status <sig-id> addressed --addressed-by <tsk-id>
```

`artifact status … addressed --addressed-by <id>` emits `<addresser> addresses <signal>` in `references.jsonl` (`src/commands/artifact-status.ts`).

## Capture → core → reflect chaining

| Chain | When |
|-------|------|
| Capture → Core | Every capture: kind from capture, schema/status from core playbooks |
| Capture → Reflect | Resolver suggests reflect after **≥ 3 captures in one session** |
| Reflect threshold | Reflect skill runs after **≥ 3 tasks** reach `done` in a short window—not after every single capture |

Beliefs and skill edits never originate in capture alone; route to `loopany-reflect` when evidence crosses the learning threshold.

## Anti-patterns

| Anti-pattern | Why it hurts |
|--------------|--------------|
| Capture on every message | Noise; breaks end-of-task discipline |
| Capture mid-work | No Outcome; premature `task` |
| Skip quality gate | Pollutes reflect and search |
| Inline capture in long sessions | Weak artifacts; use subagent dispatch |
| Fork duplicate signals/tasks | Use `--contains` then append |
| Create `learning` from one task | Wait for patterns; use reflect |

## Verification

| Check | Command / artifact |
|-------|-------------------|
| List dedup | `bun test` → `loopany artifact list` `--contains` cases in `test/cli.e2e.test.ts` |
| Quality gate | `./test/skill-regression.sh 4` (trivial work → no new files) |
| PR → task routing | `./test/skill-regression.sh 5` |
| Workspace health | `loopany doctor` after bulk capture |

## Related pages

<CardGroup>
<Card title="Skills library" href="/skills-library">
Resolver, capture, core, reflect, and review packs; routing table and harness-neutral injection.
</Card>
<Card title="Artifact commands" href="/artifact-commands">
`create`, `append`, `status`, `--addressed-by`, and Outcome conventions for terminal tasks.
</Card>
<Card title="Kinds and validation" href="/kinds-and-validation">
`task`, `signal`, and `note` schemas, status machines, and indexed fields used by `list`.
</Card>
<Card title="Reflect workflow" href="/reflect-workflow">
When capture should hand off to learnings and skill-proposals instead of writing them inline.
</Card>
<Card title="Artifact lifecycle example" href="/artifact-lifecycle-example">
End-to-end signal → task → brief flow aligned with e2e scenarios.
</Card>
</CardGroup>

---

## 12. Reflect workflow

> Gather recent outcomes and dismissed signals, pattern thresholds, write learning and skill-proposal artifacts, and accept/reject proposals with git-backed skill diffs.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/12-reflect-workflow.md
- Generated: 2026-06-05T19:00:02.604Z

### Source Files

- `skills/loopany-reflect/SKILL.md`
- `skills/loopany-core/kinds/learning.md`
- `skills/loopany-core/kinds/skill-proposal.md`
- `src/commands/artifact-list.ts`
- `src/commands/artifact-status.ts`
- `src/commands/artifact-get.ts`

---
title: "Reflect workflow"
description: "Gather recent outcomes and dismissed signals, pattern thresholds, write learning and skill-proposal artifacts, and accept/reject proposals with git-backed skill diffs."
---

The reflect workflow is agent-driven through `skills/loopany-reflect/SKILL.md`: it queries the workspace via `loopany artifact list` / `get`, applies pattern thresholds before writing `learning` or `skill-proposal` artifacts, and completes skill changes only through proposal-apply (accept/reject) with `artifact append`, `artifact status`, and a git commit — never by editing `SKILL.md` files directly.

<Info>
Reflect is not a CLI subcommand. The harness supplies deterministic artifact I/O and graph edges; judgment (pattern detection, belief synthesis, scoped edits) stays in the agent skill.
</Info>

## Modes and routing

`loopany-resolver` routes these triggers to `loopany-reflect`:

| Trigger | Mode |
|---------|------|
| `reflect`, `what have we learned`, `improve yourself`, ≥3 tasks done recently | Reflect (discover → write) |
| `accept <proposal-slug>`, `reject <proposal-slug>`, `review proposals` | Proposal apply |
| Writing a `learning` or `skill-proposal` from any source | Reflect (creation rules) |

Cross-skill chaining: capture may suggest reflect after ≥3 captures in a session; weekly review suggests reflect after ≥3 resolutions. Learning `checkAt` revalidation belongs in `loopany-review` (daily), not reflect.

```mermaid
sequenceDiagram
  participant User
  participant Resolver as loopany-resolver
  participant Reflect as loopany-reflect
  participant CLI as loopany CLI
  participant Store as artifacts + references.jsonl
  participant Git as git (skills/)

  User->>Resolver: reflect / accept proposal
  Resolver->>Reflect: load SKILL.md
  alt Reflect mode
    Reflect->>CLI: artifact list / get
    CLI->>Store: indexed + body reads
    Reflect->>Reflect: pattern thresholds
    Reflect->>CLI: artifact create (learning, skill-proposal)
    Reflect->>CLI: refs add / trace (optional)
  else Proposal apply
    Reflect->>CLI: artifact get / refs
    Reflect->>Git: edit target SKILL.md (accept only)
    Reflect->>CLI: artifact append Outcome
    Reflect->>CLI: artifact status accepted|rejected
    Reflect->>Git: commit skill + proposal artifact
  end
```

## When to run reflect

Run reflect when:

- The user explicitly asks to reflect or improve the agent.
- Weekly cadence (structural mission review stays in `loopany-review` monthly).
- At least three tasks have flipped to `done` in a short window.

<Warning>
Do not run reflect reactively after every single task. `test/skill-regression.sh` scenario 8 expects agents to decline creating a `learning` when fewer than three completed tasks with outcomes exist.
</Warning>

## Step 1 — Gather evidence

List candidates, then narrow by recency and deduplication.

<Steps>
<Step title="List artifact pools">

```bash
loopany artifact list --kind task --status done
loopany artifact list --kind signal
loopany artifact list --kind signal --status dismissed
loopany artifact list --kind learning --status active
loopany artifact list --kind skill-proposal --status rejected
```

`artifact list` returns a JSON array of `{ id, kind, path, frontmatter }`. With `--kind`, other flags map to frontmatter fields; fields listed in the kind’s `indexedFields` use O(1) index lookup (`status`, `targetSkill`, `checkAt`, etc.).

</Step>
<Step title="Filter to recent window">

Sort by `createdAt` (newest first). Default window ≈ one week.

</Step>
<Step title="Subtract already-processed evidence">

Union `evidence` from active learnings and non-rejected skill-proposals; exclude those artifact IDs from new pattern candidates.

</Step>
<Step title="Load bodies">

For each fresh candidate:

```bash
loopany artifact get <id>
# or --format json for frontmatter + body + audit history
```

Task evidence must include `## Outcome` before terminal `done`/`failed` (kind playbook; append before status flip).

</Step>
</Steps>

Optional body filter when the candidate set is still large:

```bash
loopany artifact list --kind task --status done --contains "baseline"
```

`--contains` runs last and scans bodies (and string frontmatter) case-insensitively.

## Step 2 — Pattern thresholds

Reflect only promotes **patterns**, not single anecdotes.

| Pattern | Threshold |
|---------|-----------|
| Same class of outcome | ≥ 3 tasks |
| Belief refuted | ≥ 2 tasks where an active learning predicts wrong |
| Belief needs caveat | ≥ 2 tasks |
| Signal dismissed but recurs | ≥ 3 dismissals over ≥ 2 weeks |

Good example: four metric tasks ship without `## Before` baselines → learning that outcomes are unfalsifiable → proposal requiring `## Before` in the task playbook.

Bad example: one failed outcome → no learning. A proposal whose change was already `rejected` → do not re-suggest.

## Step 3 — Write a learning

Beliefs live in `learning` artifacts (`skills/loopany-core/kinds/learning.md`). Title is the declarative belief; behavior change is optional and goes through `skill-proposal`.

<ParamField body="--kind" type="learning" required>
</ParamField>
<ParamField body="--slug" type="string" required>
Stable id for heavy `[[wiki-link]]` citation.
</ParamField>
<ParamField body="--title" type="string" required>
The belief sentence itself.
</ParamField>
<ParamField body="--evidence" type="string[]" required>
Comma-separated artifact slugs; minimum two IDs per playbook.
</ParamField>
<ParamField body="--check-at" type="date">
Revalidation date, typically 1–3 months out.
</ParamField>
<ParamField body="--mentions" type="string[]">
Soft links (also creatable via body `[[id]]`).
</ParamField>

<RequestExample>

```bash
loopany artifact create --kind learning \
  --slug short-attention-spans-2026 \
  --title "Metric tasks without baselines produce unfalsifiable outcomes" \
  --evidence "tsk-alpha,tsk-beta,tsk-gamma" \
  --mentions "mission-default" \
  --check-at 2026-07-22 \
  --content "$(cat <<'EOF'
## Observation
Four completed metric tasks recorded outcomes without before/after evidence.

## Evidence
- tsk-alpha — "Latency improved"
- tsk-beta — "Conversion up"
- tsk-gamma — "No baseline captured"

## Scope
Applies to quantitative tasks; not exploratory spikes.

## Check-at
On 2026-07-22: did new metric tasks include ## Before?
EOF
)"
```

</RequestExample>

Required body sections: `## Observation`, `## Evidence`, `## Scope`, `## Check-at`.

Status machine: `active` → `superseded` | `archived`. When understanding changes, **create a new learning** — do not edit the old file in place.

### Supersede an older belief

```bash
loopany refs add --from <new-learning> --to <old-learning> --relation supersedes
loopany artifact status <old-learning> superseded --reason "superseded by <new-learning>"
```

Append `## Outcome` to the superseded learning when flipping to `superseded` (kind required-sections rule; enforced by agent discipline).

## Step 4 — Write a skill-proposal (optional)

Create a proposal only when the learning implies a **concrete** `SKILL.md` edit. Many learnings stop at updated belief.

<ParamField body="--kind" type="skill-proposal" required>
</ParamField>
<ParamField body="--target-skill" type="string" required>
Path to `SKILL.md` (existing for `modify`/`remove`; planned path for `add`).
</ParamField>
<ParamField body="--change-type" type="enum" default="modify">
`modify` | `remove` | `add`
</ParamField>
<ParamField body="--evidence" type="string[]">
Citing tasks and/or the backing learning slug.
</ParamField>
<ParamField body="--check-at" type="date">
Schedules “did this change help?” review after accept.
</ParamField>

Required body sections:

1. `## Motivation` — cite the learning
2. `## Proposed change` — target file, intent, location, approximate content
3. `## Expected effect` — short- and long-term
4. `## Check-at` — why that date

For `changeType: add`, also include `## Skill draft` (full new `SKILL.md`) and `## Resolver entry` (row for `skills/loopany-resolver/SKILL.md`) or the skill is unreachable.

Status machine:

```mermaid
stateDiagram-v2
  [*] --> pending
  pending --> accepted: human accept + Outcome
  pending --> rejected: human reject + Outcome
```

CLI enforces transitions (`artifact status`); terminal `accepted`/`rejected` require `## Outcome` in the body per kind definition.

## Step 5 — Verify evidence chain

```bash
loopany trace <proposal-slug> --direction backward
```

Default trace relations: `led-to`, `addresses`, `supersedes`, `follows-up`, `cites` (excludes soft `mentions`). Output: `{ root, nodes[{ id, kind, distance }], edges[] }` sorted cause → root → effect.

To read explicit learning links on a proposal:

```bash
loopany refs <proposal-slug> --direction out --relation mentions
```

## Proposal apply (accept / reject)

Agents never patch skills outside this flow.

<Steps>
<Step title="List pending proposals">

```bash
loopany artifact list --kind skill-proposal --status pending
```

</Step>
<Step title="Accept">

1. `loopany artifact get <proposal-slug>`
2. `loopany refs <proposal-slug> --direction out --relation mentions` → cited learning
3. Read current `targetSkill` file
4. Apply **only** the described change
5. `loopany artifact append <proposal-slug> --section Outcome --content "..."`
6. `loopany artifact status <proposal-slug> accepted --reason "..."`
7. Git commit the skill file and proposal artifact together

</Step>
<Step title="Reject">

1. Read proposal
2. `loopany artifact append <proposal-slug> --section Outcome --content "<reason>"` (future reflect reads this)
3. `loopany artifact status <proposal-slug> rejected --reason "..."`

</Step>
</Steps>

### Accept edge cases

| Condition | Action |
|-----------|--------|
| Target `SKILL.md` missing | Reject |
| Cited learning `superseded` | Reject |
| Multiple pending proposals same file | Accept one at a time; re-read target between |

<Check>
Accepted or rejected proposals must have a non-empty `## Outcome` before status flip — empty outcomes break the self-improvement audit trail.
</Check>

## CLI surfaces reflect depends on

| Command | Role in reflect |
|---------|-----------------|
| `artifact list` | Evidence pools, dedup, pending proposals |
| `artifact get` | Outcomes, motivations, audit (`--format json`) |
| `artifact create` | New `learning` / `skill-proposal` |
| `artifact append` | `## Outcome` on accept/reject |
| `artifact status` | `accepted`/`rejected`/`superseded`; illegal transitions error |
| `refs add` / `refs` | `supersedes`, `mentions` |
| `trace` | Backward evidence chain |

<Note>
Kind required sections (`## Outcome` on terminal statuses) are declared in `skills/loopany-core/kinds/*.md` and enforced by agent workflow; `artifact status` enforces the status machine but does not yet parse body sections in TypeScript.
</Note>

## Anti-patterns

| Don't | Do instead |
|-------|------------|
| Reflect on one task | Wait for threshold table |
| Skip fresh-evidence filter | Union prior `evidence` fields first |
| `skill-proposal` without learning | Create learning first |
| Re-propose rejected change | Read `--status rejected` list |
| Edit `SKILL.md` directly | Proposal → accept → commit |
| Edit beyond proposal scope | Proposal body is the contract |
| Accept without reading learning | Verify scope against evidence |
| Run learning `checkAt` closure here | Use `loopany-review` daily + `followups` |

## Quick reference

```text
REFLECT:  list/get evidence → pattern? → learning → (optional) skill-proposal → user
ACCEPT:   get spr → refs → edit target → append Outcome → status accepted → git commit
REJECT:   get spr → append Outcome (reason) → status rejected
```

## Related pages

<CardGroup>
<Card title="Self-improvement loop" href="/self-improvement-loop">
End-to-end model: task outcomes, beliefs, proposals, and the no-direct-skill-edits rule.
</Card>
<Card title="Self-improvement example" href="/self-improvement-example">
Worked recipe from three done tasks through accept and git diff.
</Card>
<Card title="Skills library" href="/skills-library">
Resolver routing and reflect skill placement in the pack.
</Card>
<Card title="Kinds and validation" href="/kinds-and-validation">
`learning` and `skill-proposal` schemas, status machines, indexed fields.
</Card>
<Card title="Artifact commands" href="/artifact-commands">
`create`, `append`, `status`, `--slug`, `--content-file` details.
</Card>
<Card title="Reference graph" href="/reference-graph">
`refs`, `trace`, relation verbs including `supersedes` and `cites`.
</Card>
<Card title="Periodic review" href="/periodic-review">
Daily `checkAt` and weekly feed into reflect; scope boundaries.
</Card>
</CardGroup>

---

## 13. Periodic review

> Daily followups (--due today), weekly doctor + overdue sweep, monthly mission-drift checks, and closure rules for checkAt-bearing artifacts.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/13-periodic-review.md
- Generated: 2026-06-05T19:00:54.642Z

### Source Files

- `skills/loopany-review/SKILL.md`
- `skills/loopany-review/references/daily.md`
- `skills/loopany-review/references/weekly.md`
- `skills/loopany-review/references/monthly.md`
- `src/commands/followups.ts`
- `src/commands/doctor.ts`

---
title: "Periodic review"
description: "Daily followups (--due today), weekly doctor + overdue sweep, monthly mission-drift checks, and closure rules for checkAt-bearing artifacts."
---

Periodic review is implemented as the `loopany-review` skill pack (`skills/loopany-review/`) plus deterministic CLI queries (`loopany followups`, `loopany doctor`). The harness returns candidates; the agent classifies, surfaces only judgment-needed items, dispatches to per-kind playbooks, and must close every surfaced item before ending the session.

## Role in the loop

| Layer | Responsibility |
|-------|----------------|
| `loopany-resolver` | Routes phrases like "what's due today", "weekly check", or "is this still the right mission?" to `loopany-review` |
| `loopany-review` | One skill, three frequencies; shared classify → surface → dispatch → **close** gate |
| `loopany followups` | Index scan: artifacts with `checkAt` on or before a cutoff date |
| `loopany doctor` | Integrity checks (workspace, schema, kinds, artifacts, references, onboarding, warnings) |

<Note>
Review is judgment-heavy and runs inside an agent session. `INSTALL_FOR_AGENTS.md` treats bare shell cron as insufficient for weekly/monthly work; coding CLIs default to session-boundary prompts instead of durable cron registration.
</Note>

## Cadence routing

Do not mix scopes across frequencies — one day of overdue is not a weekly pattern; one week cannot prove mission drift.

| Frequency | Scope | Typical trigger | Reference |
|-----------|-------|-----------------|-----------|
| **Daily** | `checkAt` due **today** (not overdue backlog) | Session start, "what's due today" | `skills/loopany-review/references/daily.md` |
| **Weekly** | Overdue `checkAt`, `doctor`, parking-lot sweeps | "What's slipping", "weekly check", workspace health | `skills/loopany-review/references/weekly.md` |
| **Monthly** | Active mission alignment, structural drift (domains/kinds) | "Right mission?", "anything structural?" | `skills/loopany-review/references/monthly.md` |

```mermaid
flowchart TB
  subgraph triggers [Resolver triggers]
    T1["what's due today"]
    T2["weekly check / slipping"]
    T3["right mission? / structural?"]
  end
  subgraph skill [loopany-review]
    D[daily.md]
    W[weekly.md]
    M[monthly.md]
    U["Unified flow: classify → surface → dispatch → close"]
  end
  subgraph cli [Deterministic CLI]
    F["loopany followups"]
    DOC["loopany doctor"]
    AL["loopany artifact list/get/status/set"]
  end
  T1 --> D --> U
  T2 --> W --> U
  T3 --> M --> U
  D --> F
  W --> F
  W --> DOC
  M --> AL
  U --> AL
```

## Unified review flow

All frequencies share the same pipeline after frequency-specific queries.

<Steps>
<Step title="Query">
Run the commands from the daily, weekly, or monthly reference file.
</Step>
<Step title="Classify">
For each candidate: `loopany artifact get <id>`.

- **A — Silently resolvable:** extend `checkAt` with a one-line reason (no user prompt).
- **B — Needs the user:** judgment or data only the user has.
- **C — Defer:** push `checkAt` forward **with a reason** ("not yet" is invalid).

For `learning` artifacts, add context: `loopany trace <learning-slug> --direction backward`.
</Step>
<Step title="Surface">
Emit only bucket **B** items. One line each. If empty, say so and stop — quiet is correct.
</Step>
<Step title="Dispatch">
Route to kind playbooks under `skills/loopany-core/kinds/` (`task`, `learning`, `signal`, `mission`).
</Step>
<Step title="Close (gate)">
Every surfaced item must end as **resolved**, **deferred**, or **retired** before the session ends. A digest without state changes is noise.
</Step>
</Steps>

### Closure outcomes

| Outcome | Meaning | Typical writes |
|---------|---------|----------------|
| **Resolved** | Question answered or work finished | `loopany artifact status`, required `## Outcome` on terminal task statuses; signal → `addressed` with `--addressed-by` |
| **Deferred** | Revisit later with intent | `loopany artifact set <id> --field checkAt --value <YYYY-MM-DD>` plus reason in body or append |
| **Retired** | No further scheduled check | Remove follow-up obligation (`checkAt` cleared or artifact reaches terminal status) with a note |

```mermaid
stateDiagram-v2
  [*] --> Surfaced: bucket B item
  Surfaced --> Resolved: status transition + evidence
  Surfaced --> Deferred: checkAt pushed with reason
  Surfaced --> Retired: checkAt removed or terminal status
  Resolved --> [*]
  Deferred --> [*]
  Retired --> [*]
  note right of Surfaced
    No zombie items:
    session must not end
    with open surfaced items
  end note
```

## Daily review

**Query**

```bash
loopany followups --due today
```

**Rules**

- Process only items whose `checkAt` date equals **today**. The CLI returns every artifact with `checkAt <= today`, which includes overdue rows — leave those for weekly (`--due overdue`).
- At most once per day.
- Empty result → report "nothing due today" and stop.

**Example surface format** (from the skill; slugs are illustrative):

```
3 things need you today:
1. [<task-slug>] 2-week recheck — did $/session settle?
2. [<learning-slug>] "short deals close 2.5x faster" — still true?
3. [<signal-slug>] recurring churn signal, 3rd time. Upgrade?
```

`learning` revalidation on `checkAt` belongs in daily review, not `loopany-reflect` (reflect looks forward; review looks back at scheduled checks).

## Weekly review

**Query**

```bash
loopany followups --due overdue
loopany doctor --format json
```

Plus parking-lot lists (agent-run `artifact list`, not a separate CLI):

| Parking lot | Command | Threshold |
|-------------|---------|-----------|
| Stalled tasks | `artifact list --kind task --status running` | ≥ 14 days, no recent append |
| Stale signals | `artifact list --kind signal --status open` | ≥ 7 days, no action |
| Pending proposals | `artifact list --kind skill-proposal --status pending` | any → mention; > 5 → nudge |

**Doctor handling**

Summarize by category and propose fixes. Do **not** auto-fix — failures may reflect an in-progress decision.

Stalled `running` tasks should move to `in_review`, `failed`, or `cancelled` with `## Outcome`. "Still working on it" is not closure.

**Feed reflect**

If the weekly pass resolves ≥ 3 items, suggest `loopany-reflect` (pattern extraction is reflect's job, not doctor's).

## Monthly review

**Query**

```bash
loopany artifact list --kind mission --status active
```

Plus recent tasks (~30 days) for alignment math.

**Mission alignment** (from `skills/loopany-core/kinds/mission.md`):

```
alignment = tasks mentioning this mission / total recent tasks
```

| Alignment | Action |
|-----------|--------|
| ≥ 60% | Healthy |
| 30–60% | Partial drift — backfill `mentions` or add a second mission? |
| < 30% | Clear drift — propose re-onboarding |

Monthly surfaces hypotheses; the user decides. Do not auto-abandon missions.

**Structural drift — domains** (all three required before proposing a `[change]` task):

1. ≥ 5 artifacts tagged with an unenabled domain
2. ≥ 2 weeks of accumulation
3. Scope distinguishable from existing domains

**Structural drift — kinds** (both required):

1. ≥ 3 `note` artifacts sharing a body skeleton for ≥ 2 weeks
2. Passes the four-question kind test in `skills/loopany-core/conventions/taxonomy.md`

Forward-only: propose new structure; do not migrate existing artifacts in review.

## `checkAt` and `loopany followups`

`checkAt` is optional frontmatter on kinds that index it (`task`, `learning`, `skill-proposal`, …). Set it only with a concrete future question — missing `checkAt` is better than a date you will ignore.

The in-memory index (`src/core/index.ts`) selects artifacts where `checkAt` (YYYY-MM-DD prefix) is on or before the cutoff date. `src/commands/followups.ts` adds filters:

<ParamField body="--due" type="today | overdue | next-7d" default="today">
Cutoff for the index scan. `next-7d` extends the cutoff seven days ahead (implementation supports it; `--help` lists `today` and `overdue` only).
</ParamField>

<ParamField body="--include-done" type="true">
When `true`, includes artifacts whose `status` has no outgoing transitions in the kind status machine (e.g. `done`, `cancelled`, `failed` on tasks).
</ParamField>

<ParamField body="--domain" type="string">
Restricts results to matching `frontmatter.domain`.
</ParamField>

| `--due` value | Index cutoff | Extra filter |
|---------------|--------------|--------------|
| `today` (default) | `checkAt <= today` | None |
| `overdue` | `checkAt <= today` | `checkAt` date **strictly before** today |
| `next-7d` | `checkAt <= today + 7 days` | None |

**Output:** JSON array of `ArtifactMeta` on stdout (same as other query commands).

**Create / defer examples**

```bash
loopany artifact create --kind task --title "Recheck API rate limits" \
  --status todo --check-at 2026-06-12

loopany artifact set <id> --field checkAt --value 2026-07-01
```

E2E coverage: `test/cli.e2e.test.ts` (`loopany followups` describes terminal-status filtering); lifecycle scenario in `test/scenario.e2e.test.ts` (signal → task with `checkAt` → `followups --due today` → `done`).

## `loopany doctor`

Doctor is integrity-only: deterministic checks, no semantic body scans (TODO hunting belongs in reflect).

| Check | Pass | Fail / warn behavior |
|-------|------|----------------------|
| `workspace` | Bootstrap path + kinds dir | — |
| `schema version` | `config.schemaVersion` matches binary `SCHEMA_VERSION` | Fail; message points to `loopany migrate` |
| `kinds` | All kind defs parse | Fail lists broken files |
| `artifacts` | Every frontmatter passes kind Zod schema | Fail lists invalid IDs |
| `references` | No dangling `from`/`to` | Fail lists broken edges |
| `onboarding` | `self` person + ≥1 active `mission` | Fail with onboarding hint |
| `mission coverage` | (warn) every `task` mentions a mission | Warn lists orphans |
| `domain coverage` | (warn) artifact `domain` ∈ `enabled_domains` | Warn lists unenabled domains |

<CodeGroup>
```bash title="Human-readable"
loopany doctor
```

```bash title="JSON (weekly review)"
loopany doctor --format json
```
</CodeGroup>

Exit code is non-zero when any check has `status: fail`. Warnings do not fail the run.

Version check is bypassed during doctor bootstrap so a mismatch is reported instead of crashing early.

## Boundaries and anti-patterns

| Mistake | Why it fails |
|---------|----------------|
| Daily pass processes overdue backlog | Steals weekly scope; skill explicitly forbids |
| Weekly pass re-runs daily due-today | Nothing should stagnate in 24h at weekly granularity |
| Monthly pass duplicates weekly parking lots | One week cannot prove structural drift |
| Defer without reason | Items rot silently |
| Surface without close | Worse than no digest |
| Dump raw `followups` JSON | Agent job is judgment, not relay |
| Run reflect for due `learning` checks | Scheduled revalidation is daily review |

## Registering cadence

On install, agents pick one path silently (`INSTALL_FOR_AGENTS.md`):

- **Durable cron hosts (Hermes, OpenClaw, …):** register `loopany-review` (and `loopany-reflect` weekly) on schedule inside agent sessions; record job IDs for audit.
- **Coding CLIs (default):** do not register cron (e.g. Claude Code `CronCreate` expires ~7 days). Instead: session start → daily `followups`; week-end → weekly doctor + overdue; month-end → mission review.

The user hears a single plain-language cadence sentence at onboarding close — not host-internals menus.

## Related pages

<CardGroup>
<Card title="Graph, search, and scheduling" href="/graph-search-commands">
`followups` flags, JSON output, and terminal-status filtering in full command context.
</Card>
<Card title="Doctor and troubleshooting" href="/doctor-and-troubleshooting">
Per-check failures, exit codes, and recovery alongside `migrate` / `reindex`.
</Card>
<Card title="Reflect workflow" href="/reflect-workflow">
Weekly feed when ≥3 resolutions; learnings and skill-proposals — not daily `checkAt` revalidation.
</Card>
<Card title="Self-improvement loop" href="/self-improvement-loop">
`checkAt` on accepted skill-proposals and learning beliefs that daily/weekly review closes.
</Card>
<Card title="Workspace setup" href="/workspace-setup">
`loopany init`, onboarding, and cadence registration expectations after install.
</Card>
<Card title="Skills library" href="/skills-library">
Resolver routing table and cross-skill chaining (review → core, review → reflect).
</Card>
</CardGroup>

---

## 14. Schema migration

> schemaVersion vs binary VERSION, SchemaVersionMismatchError recovery, loopany migrate discovery, and v0.1.0→v0.2.0 script-driven workspace transforms.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/14-schema-migration.md
- Generated: 2026-06-05T19:00:35.872Z

### Source Files

- `src/version.ts`
- `src/core/engine.ts`
- `src/commands/migrate.ts`
- `skills/migrations/v0.1.0-to-v0.2.0/SKILL.md`
- `skills/migrations/README.md`
- `test/migration-v0.1-to-v0.2.e2e.test.ts`
- `test/migration-framework.test.ts`

---
title: "Schema migration"
description: "schemaVersion vs binary VERSION, SchemaVersionMismatchError recovery, loopany migrate discovery, and v0.1.0→v0.2.0 script-driven workspace transforms."
---

Every `loopany` command except `migrate` and `doctor` calls `bootstrap()` in `src/core/engine.ts`, which compares `$LOOPANY_HOME/config.yaml` `schemaVersion` to the compile-time `SCHEMA_VERSION` constant in `src/version.ts`. A mismatch throws `SchemaVersionMismatchError` and blocks artifact, graph, and search operations until you run the Bun scripts under `skills/migrations/v<from>-to-v<to>/` — the CLI only discovers and documents those scripts; it does not execute them.

## Two independent version axes

| Identifier | Location | Meaning |
| --- | --- | --- |
| `VERSION` | `src/version.ts` | Semver of the **binary** (`loopany --version`). Bumps on any release. |
| `SCHEMA_VERSION` | `src/version.ts` | On-disk **workspace format** the binary expects. Bumped only when kinds, frontmatter, or references change in a way that needs migration. |
| `schemaVersion` | `$LOOPANY_HOME/config.yaml` | The workspace’s **claimed** format version. |

Both `VERSION` and `SCHEMA_VERSION` are currently `0.2.0`, but they are documented to drift independently: you can ship CLI `0.2.1` without changing workspace layout, or bump `SCHEMA_VERSION` to `0.3.0` and ship a migration skill without a binary feature release.

<Note>
`src/core/search-store.ts` defines a separate SQLite `SCHEMA_VERSION` for `search.db`. That index schema is unrelated to workspace `config.yaml#schemaVersion`.
</Note>

### Legacy default

If `config.yaml` exists but omits `schemaVersion`, `Config.load()` assumes `0.1.0` (`ASSUMED_LEGACY_VERSION` in `src/core/config.ts`). Pre-framework workspaces never wrote the field. `loopany init` always writes the current `SCHEMA_VERSION` when it creates a new config file; it does not overwrite an existing `config.yaml`.

## Bootstrap guard and recovery

```mermaid
stateDiagram-v2
  [*] --> Stale: schemaVersion ≠ SCHEMA_VERSION
  Stale --> Blocked: bootstrap() without skip
  Blocked --> MismatchError: SchemaVersionMismatchError
  MismatchError --> Discover: loopany migrate
  Discover --> RunScripts: bun run skills/migrations/.../scripts/*.ts --apply
  RunScripts --> Current: schemaVersion = SCHEMA_VERSION
  Current --> [*]: normal CLI commands succeed
  Stale --> DoctorReport: loopany doctor (skipVersionCheck)
  DoctorReport --> Discover: schema version check fails with migrate hint
```

On mismatch, `SchemaVersionMismatchError` embeds the workspace version, the binary’s expected version, the path to the matching migration skill, and the suggested CLI name:

```
Workspace schema is v0.1.0 but this binary expects v0.2.0.
Read `skills/migrations/v0.1.0-to-v0.2.0/SKILL.md`
and run `loopany migrate v0.1.0-to-v0.2.0`.
```

`src/cli.ts` catches this error class, prints the message to stderr, and exits with code `1` — the same path used for `WorkspaceNotFoundError` and Zod validation failures.

### Commands that bypass the guard

| Surface | Bypass mechanism |
| --- | --- |
| `loopany migrate` | `bootstrap({ skipVersionCheck: true })` |
| `loopany doctor` | `bootstrap({ skipVersionCheck: true })` — reports mismatch as a **fail** check instead of crashing |
| Migration scripts | Set `LOOPANY_SKIP_VERSION_CHECK=1` in the environment (also honored by `bootstrap`) |

<Warning>
Do not leave `LOOPANY_SKIP_VERSION_CHECK=1` in your shell profile. It disables the guard for every command in that session.
</Warning>

After migration completes and `schemaVersion` matches `SCHEMA_VERSION`, regular commands (`artifact list`, `refs`, `search`, etc.) bootstrap normally again.

## `loopany migrate` — discovery only

`src/commands/migrate.ts` scans `skills/migrations/` next to the repo’s `skills/loopany-core/`. Directory names must match `v<semver>-to-v<semver>` (full three-part semver, e.g. `v0.1.0-to-v0.2.0`).

| Invocation | Behavior |
| --- | --- |
| `loopany migrate` | Prints workspace `schemaVersion`, binary `SCHEMA_VERSION`, all known migrations, and **next** migration where `from` equals the workspace version |
| `loopany migrate <name>` | Prints migration metadata, ordered `scripts/*.ts` paths, and the full `SKILL.md` body |

The command **does not** run migration logic. Scripts stay standalone so you can dry-run, apply step-by-step, inspect intermediate disk state, and abort without the binary partially mutating runtime state through coupled `--run` flags.

<RequestExample>

```bash
loopany migrate
```

</RequestExample>

<ResponseExample>

```text
Workspace schema: v0.1.0
Binary expects:   v0.2.0

Next migration:   v0.1.0-to-v0.2.0
                  loopany migrate v0.1.0-to-v0.2.0

Available migrations (1):
  v0.1.0-to-v0.2.0
```

</ResponseExample>

On an up-to-date workspace, list output includes `Up to date — no migration needed.` If the workspace version has no matching `from` directory, you get `No migration found from v…` (file an issue — the binary should ship a chain for every supported jump).

## Migration pack layout

:::files
skills/migrations/
  README.md
  v<from>-to-v<to>/
    SKILL.md
    scripts/
      01-<step>.ts
      ...
      05-bump-version.ts
      06-refresh-kinds.ts
:::

Authoring rules from `skills/migrations/README.md`:

- **One migration = one minor version jump** — chain `v0.1.0-to-v0.2.0` then `v0.2.0-to-v0.3.0` instead of multi-hop scripts.
- **Scripts are standalone** — no imports from `src/core/`; they parse the **old** on-disk format the current binary may no longer understand.
- **Dry-run by default** — pass `--apply` to commit writes.
- **Idempotent** — reruns should be no-ops once the workspace is at the target shape.
- **Workspace-rooted** — read `LOOPANY_HOME` (default `~/loopany`), not the process cwd.
- **Final step must bump `schemaVersion`** — e.g. `05-bump-version.ts` writes `0.2.0` to `config.yaml`.

The skill (`SKILL.md`) carries rationale, pre-flight, ordered steps, rollback, and verification. The CLI is an index; the agent (or operator) runs `bun run` on each script.

## v0.1.0 → v0.2.0 transforms

The shipped migration `skills/migrations/v0.1.0-to-v0.2.0/` is the first major workspace simplification: slug-as-ID storage, flat `artifacts/<dirName>/` paths, camelCase frontmatter, auto timestamps, and a `journal` kind as the time spine.

| Area | v0.1.0 | v0.2.0 |
| --- | --- | --- |
| Kind defs | `idPrefix`, `bodyMode`, `storage`, `idStrategy` | Optional `slugLayout`; slugs are global IDs |
| Artifact paths | `artifacts/{YYYY-MM}/` for date-bucketed kinds | `artifacts/<dirName>/<slug>.md` |
| IDs | Prefixed (`tsk-20260428-091500`) | Bare slug (`20260428-091500`) |
| Frontmatter | snake_case (`check_at`) | camelCase (`checkAt`) + `createdAt` / `updatedAt` |
| Provenance | — | `_backfilled: true` on migrated rows |
| Time index | Implicit in paths | `artifacts/journal/<YYYY>/<YYYY-MM-DD>.md` backfilled from `createdAt` |
| Graph | `references.jsonl` with old IDs | Rewritten via `.migration-id-map.json` |

Crewlet-specific kinds and fields are intentionally **not** migrated — only loopany core shapes.

### Script pipeline

| Step | Script | Role |
| --- | --- | --- |
| 1 | `01-snapshot.ts` | Read-only counts, collision detection (exit `1` if two old IDs map to the same slug) |
| 2 | `02-transform-artifacts.ts` | Rewrite frontmatter, flatten paths, rewrite `[[wiki-links]]`, write `.migration-id-map.json` |
| 3 | `03-rebuild-references.ts` | Rewrite `references.jsonl` using the id map; keeps `references.jsonl.v0.1.bak` |
| 4 | `04-build-journal.ts` | One journal file per distinct `createdAt` date |
| 5 | `05-bump-version.ts` | Sets `config.yaml` `schemaVersion: 0.2.0` — **gate** for bootstrap |
| 6 | `06-refresh-kinds.ts` | Overwrites `kinds/*.md` from bundled v0.2 defs (`init` never overwrites existing kind files) |

Shared helpers live in `scripts/_lib.ts` and intentionally duplicate v0.1 parsing logic.

<Steps>
<Step title="Pre-flight snapshot">

```bash
cd $LOOPANY_HOME
git init -q 2>/dev/null || true
git add -A && git commit -q -m "pre-migration snapshot v0.1.0" || true
loopany doctor > /tmp/loopany-pre-migration-doctor.txt 2>&1 || true
```

Fix any existing `doctor` **fail** checks before migrating — scripts assume a healthy v0.1 workspace.

</Step>
<Step title="Discover migration">

```bash
loopany migrate
loopany migrate v0.1.0-to-v0.2.0   # prints SKILL.md + script list
```

</Step>
<Step title="Run scripts (dry-run, then apply)">

```bash
SKILL=skills/migrations/v0.1.0-to-v0.2.0/scripts
export LOOPANY_HOME=~/loopany
export LOOPANY_SKIP_VERSION_CHECK=1

bun run $SKILL/01-snapshot.ts
bun run $SKILL/02-transform-artifacts.ts
bun run $SKILL/02-transform-artifacts.ts --apply
bun run $SKILL/03-rebuild-references.ts --apply
bun run $SKILL/04-build-journal.ts --apply
bun run $SKILL/05-bump-version.ts --apply
bun run $SKILL/06-refresh-kinds.ts --apply
```

</Step>
<Step title="Verify">

```bash
unset LOOPANY_SKIP_VERSION_CHECK
loopany doctor
loopany artifact list --kind task
loopany artifact list --kind journal
```

Expect `schema version: ok`, no `tsk-`/`sig-` prefixes on IDs, flat `artifacts/` dirs (no `{YYYY-MM}/` buckets), and journal entries under `artifacts/journal/<year>/`.

</Step>
</Steps>

### Slug collisions

If step 02 would assign the same slug to two artifacts (e.g. `mis-self` and `prs-self` both → `self`), `01-snapshot.ts` fails loud. Rename one source file before `--apply`, then rerun from snapshot.

### Rollback

```bash
cd $LOOPANY_HOME
git reset --hard HEAD
rm -f .migration-id-map.json
```

All migration mutations stay inside the workspace tree; the pre-flight git commit is the primary recovery path.

<Info>
`loopany init` is idempotent for existing files — it will not refresh stale `kinds/*.md`. Step `06-refresh-kinds.ts` exists precisely because post-migration kind defs must match v0.2 validators.
</Info>

## Authoring a future migration

When changing on-disk format:

1. Bump `SCHEMA_VERSION` in `src/version.ts` and add `skills/migrations/v<from>-to-v<to>/` with `SKILL.md` + numbered scripts.
2. Use **minor** bumps for additive or transformable changes; reserve **major** for breaks no script can bridge.
3. End with a script that calls `config.setSchemaVersion('<to>')` or equivalent YAML write.
4. Add `test/migration-*.e2e.test.ts` that builds a fixture old workspace, runs scripts via `bun run` with `LOOPANY_HOME` set, and asserts `loopany doctor` passes.

There are **no backwards-compat shims** in the binary for old formats — migration is the only supported forward path once a new `SCHEMA_VERSION` ships.

## Test coverage

| Test file | What it proves |
| --- | --- |
| `test/migration-framework.test.ts` | `schemaVersion` load/default/persist, init writes version, bootstrap guard, `LOOPANY_SKIP_VERSION_CHECK`, `migrate` list/describe, doctor surfaces mismatch |
| `test/migration-v0.1-to-v0.2.e2e.test.ts` | Full six-script pipeline on a synthetic v0.1 workspace; id map, paths, camelCase, refs rewrite, journal backfill, kind refresh, doctor clean, CLI unblocked |

## Related pages

<CardGroup>
<Card title="Configuration reference" href="/configuration-reference">
`schemaVersion`, `LOOPANY_HOME`, and `LOOPANY_SKIP_VERSION_CHECK` in config and environment.
</Card>
<Card title="CLI reference" href="/cli-reference">
`migrate` and `doctor` flags, JSON output, and command inventory.
</Card>
<Card title="Artifacts and workspace" href="/artifacts-and-workspace">
v0.2 slug-as-id layout, `artifacts/<dirName>/`, and journal storage after migration.
</Card>
<Card title="Doctor and troubleshooting" href="/doctor-and-troubleshooting">
Schema version check in doctor output and recovery when commands refuse to run.
</Card>
<Card title="Workspace setup" href="/workspace-setup">
Fresh `loopany init` writes current `schemaVersion` without migrating.
</Card>
</CardGroup>

---

## 15. CLI reference

> Full command surface from loopany --help: init, artifact/*, refs, trace, domain, followups, search, reindex, factory, kind list, doctor, migrate, and JSON stdout conventions.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/15-cli-reference.md
- Generated: 2026-06-05T19:01:26.703Z

### Source Files

- `src/cli.ts`
- `src/version.ts`
- `src/core/operations.ts`
- `src/commands/argv.ts`
- `test/cli.e2e.test.ts`
- `test/helpers/cli.ts`

---
title: "CLI reference"
description: "Full command surface from loopany --help: init, artifact/*, refs, trace, domain, followups, search, reindex, factory, kind list, doctor, migrate, and JSON stdout conventions."
---

The `loopany` binary (currently **0.2.0**, workspace schema **0.2.0**) is a Bun entrypoint at `src/cli.ts` that dispatches hand-parsed argv to command modules, bootstraps `$LOOPANY_HOME` (default `~/loopany`), and prints **pretty-printed JSON** on stdout for most mutating and query operations. Human prose is reserved for `init`, default `doctor`, `migrate`, and the long-running `factory` server.

```text
loopany [--help|-h] [--version|version]
        init
        kind list
        artifact create|get|list|append|status|set
        refs <id> | refs add
        trace <id>
        followups
        domain list|enable|disable
        search <query> | reindex
        factory
        doctor | migrate [<name>]
```

<Note>
There is no global `--json` flag. JSON vs text is per command (see [Stdout conventions](#stdout-conventions)). Flags always take a value (`--flag value`); exceptions are bare switches on `reindex` (`--force`, `--no-embed`) and `factory` (`--no-open`).
</Note>

## Runtime and workspace

| Input | Effect |
| --- | --- |
| `LOOPANY_HOME` | Workspace root; default `~/loopany` |
| `LOOPANY_SKIP_VERSION_CHECK=1` | Skips `config.yaml` `schemaVersion` guard (migration scripts) |
| `LOOPANY_EDITOR` | Used by factory UI when opening artifact files (not by core CLI) |

Bootstrap (`bootstrap()` in `src/core/engine.ts`) requires `kinds/` under the workspace. If `schemaVersion` ≠ binary `SCHEMA_VERSION` and the skip flag is unset, commands fail with `SchemaVersionMismatchError` and a pointer to `loopany migrate`. `doctor` and `migrate` bypass that guard so they can report or plan upgrades.

Every completed invocation appends one line to `audit.jsonl` with `op` (dotted, e.g. `artifact.create`), `actor: cli`, `duration_ms`, and command-specific metadata; failures include `error`. Body content is never logged.

## Argv parsing

`src/commands/argv.ts` implements a minimal parser: positional tokens first, then repeating `--key value` pairs. Unknown flags on `artifact create` produce a field list derived from the kind definition. Kebab-case flags map to camelCase frontmatter (`--check-at` → `checkAt`). Comma-separated values become `string[]` (`--mentions alice,bob`).

## Command reference

### `loopany init`

Scaffolds the workspace: `kinds/`, `artifacts/`, `config.yaml` with current `schemaVersion`, and copies bundled kinds from `skills/loopany-core/kinds/`. Idempotent — existing files are left untouched.

| Stream | Shape |
| --- | --- |
| stdout | Human lines: path, created count, optional ONBOARDING hint |
| stderr | — |
| exit | `0` |

Onboarding hint appears when no `artifacts/missions/*.md` exists.

### `loopany kind list`

| Stream | Shape |
| --- | --- |
| stdout | JSON array of `{ kind, dirName, slugLayout, indexedFields, hasStatusMachine }` |
| exit | `0`; non-zero if workspace missing |

Domain pack kinds appear only when the domain is enabled (`domain enable`).

### Artifact commands

All artifact subcommands except `get` (default) emit JSON. Per-kind fields come from `~/loopany/kinds/<kind>.md`; reserved flags on create: `--kind`, `--slug`, `--domain`, `--content`, `--content-file`.

#### `artifact create`

```
loopany artifact create --kind <K> [--slug <id>] [--domain <D>]
  [--content <text> | --content-file <path|->]
  [--<field> <value> ...]
```

<ParamField body="--kind" type="string" required>
Kind name; must exist in the loaded registry.
</ParamField>

<ParamField body="--slug" type="string">
Stable id for `[[citations]]`. If omitted: slugified `--title` with collision suffixes, or timestamp-based id when no title.
</ParamField>

<ParamField body="--content / --content-file" type="string">
Body text. `-` on `--content-file` reads stdin. Inline `--content` rejects literal `\n` without real newlines (shell footgun guard).
</ParamField>

<ResponseField name="stdout" type="object">
`{ "id", "kind", "path" }` — path is the on-disk markdown file.
</ResponseField>

<Warning>
`--kind journal` is rejected; journal entries are auto-managed when other artifacts are created.
</Warning>

#### `artifact get <id>`

<ParamField body="--format" type="md | json" default="md">
`md`: raw file contents. `json`: `{ id, kind, path, frontmatter, body, audit }` where `audit` lists mutation ops for this id (timestamps as local `YYYY-MM-DD HH:MM`, no `actor`/`duration_ms`).
</ParamField>

#### `artifact list`

```
loopany artifact list [--kind K] [--status S] [--domain D]
  [--<field> V ...] [--contains Q]
```

<ResponseField name="stdout" type="array">
`[{ id, kind, path, frontmatter }, ...]`. With `--kind`, extra `--field` filters use the per-kind index when declared in `indexedFields`; otherwise linear scan. `--contains` is case-insensitive on body and string/`string[]` frontmatter; applied last.
</ResponseField>

#### `artifact append <id>`

Requires `--section` and (`--content` or `--content-file`). Returns `{ "id" }`.

#### `artifact status <id> <new-status>`

<ParamField body="--reason" type="string">
Recorded in audit, not appended to the artifact body.
</ParamField>

<ParamField body="--addressed-by" type="string">
Required when transitioning to `addressed`; emits an `addresses` edge from that id to this artifact.
</ParamField>

<ResponseField name="stdout" type="object">
`{ id, status, reason?, addressedBy?, edgesEmitted }`.
</ResponseField>

Status transitions are enforced by each kind’s status machine in its kind file.

#### `artifact set <id>`

```
loopany artifact set <id> --field <name> --value <value>
```

Updates one non-status frontmatter field. Use `artifact status` for `status`.

<ResponseField name="stdout" type="object">
`{ id, field, value }`.
</ResponseField>

### Graph commands

#### `refs <id>`

BFS over the in-memory graph (persisted `references.jsonl` plus implicit edges from `mentions` frontmatter and body `[[id]]` links).

| Flag | Default | Meaning |
| --- | --- | --- |
| `--direction` | `out` | `in`, `out`, or `both` |
| `--depth` | `1` | Positive integer hop count |
| `--relation` | — | Filter by relation verb |
| `--domain` | — | Both endpoints must have this `domain` frontmatter |

<ResponseField name="stdout" type="array">
`Edge[]`: `{ ts, from, to, relation, actor, implicit? }`.
</ResponseField>

#### `refs add`

```
loopany refs add --from <id> --to <id> --relation <R>
```

Appends one row to `references.jsonl`. Returns the new edge object.

#### `trace <id>`

Walks causal predicates to a fixed point (not plain BFS depth). Default relations: `led-to`, `addresses`, `supersedes`, `follows-up`, `cites` (`mentions` excluded).

| Flag | Default |
| --- | --- |
| `--direction` | `both` (`forward`, `backward`, `both`) |
| `--relations` | default set (comma-separated override) |
| `--max-depth` | unlimited |

<ResponseField name="stdout" type="object">
`{ root, nodes: [{ ...ArtifactMeta, distance }], edges: Edge[] }` — `distance` negative = causes, `0` = root, positive = effects; nodes sorted by distance.
</ResponseField>

### `followups`

```
loopany followups [--due today|overdue|next-7d] [--include-done true] [--domain D]
```

<ResponseField name="stdout" type="array">
`ArtifactMeta[]` with `checkAt` due per mode. Terminal statuses (no outgoing transitions in the kind machine) are excluded unless `--include-done true`.
</ResponseField>

### Domain commands

| Command | stdout |
| --- | --- |
| `domain list` | `{ enabled: string[], observed_only: string[] }` — observed = domains seen on artifacts but not in `config.yaml` |
| `domain enable <name>` | `{ enabled: string[] }` |
| `domain disable <name>` | `{ enabled: string[] }` |

Enabling loads `domains/<name>/kinds/*.md` into the registry on next bootstrap.

### Search and index

#### `reindex`

```
loopany reindex [--force] [--no-embed]
```

Rebuilds `$LOOPANY_HOME/search.db` from all artifacts. Incremental by file mtime unless `--force` (deletes DB first). `--no-embed` uses FTS5-only (`embedder: "noop"`).

<ResponseField name="stdout" type="object">
`{ indexed, skipped, removed, embedder: "transformers" | "noop" }`.
</ResponseField>

Artifact writes do not auto-reindex; run `reindex` after bulk edits.

#### `search <query>`

```
loopany search <query> [--kind K] [--domain D] [--status S] [--limit N]
```

Default `--limit` is `10`. Hybrid FTS5 + embeddings when the embedder is available.

<ResponseField name="stdout" type="array">
`SearchResult[]`: `{ artifactId, kind, domain, status, path, section, snippet, score }`.
</ResponseField>

If `search.db` is missing: exit `0`, stdout `[]`, stderr warns to run `reindex` first.

### `factory`

```
loopany factory [--port N] [--host H] [--no-open]
```

Starts the pixel-factory UI (default port **4242**, host **127.0.0.1**). Prints URL and workspace path; opens the browser unless `--no-open`. Runs until SIGINT/SIGTERM — no JSON stdout.

### `doctor`

```
loopany doctor [--format json]
```

Checks: workspace, schema version, kinds parse, artifact frontmatter validation, reference integrity (no dangling edges), onboarding (`self` person + active mission), mission coverage (warn), domain coverage (warn).

| `--format` | stdout | exit |
| --- | --- | --- |
| (default) | Human checklist (`✓` / `⚠` / `✗`) | `1` if any check `fail` |
| `json` | `{ workspace, checks: [{ name, status, detail, problems? }], ok }` | same |

Schema version mismatch is reported as `fail` with `loopany migrate …` in `problems`.

### `migrate`

```
loopany migrate
loopany migrate <name>   # e.g. v0.1.0-to-v0.2.0
```

Does **not** run migration scripts — discovery and documentation only. Scripts live under `skills/migrations/v<from>-to-v<to>/scripts/` and are run with `bun` by the agent.

| Invocation | stdout |
| --- | --- |
| no args | Human: workspace vs binary schema, next migration hint, available list |
| `<name>` | Human: migration metadata, script paths, full `SKILL.md` body |

Uses `bootstrap({ skipVersionCheck: true })`.

### Version

`loopany --version` or `loopany version` → `loopany 0.2.0\n` (plain text).

## Stdout conventions

```mermaid
flowchart LR
  subgraph human["Human stdout"]
    init[init]
    doctorH[doctor default]
    migrate[migrate]
    factory[factory server]
    version[--version]
  end
  subgraph json["JSON stdout pretty-printed"]
    kinds[kind list]
    artifacts[artifact mutators + list]
    graph[refs trace followups]
    domains[domain *]
    searchCmd[search reindex]
    doctorJ[doctor --format json]
    getJ[artifact get --format json]
  end
  cli[src/cli.ts dispatch] --> human
  cli --> json
```

| Pattern | Commands |
| --- | --- |
| `JSON.stringify(x, null, 2) + '\n'` | `kind list`, `artifact` (except default `get`), `refs`, `trace`, `followups`, `domain *`, `search`, `reindex` |
| Raw markdown | `artifact get` (default) |
| Human prose | `init`, `migrate`, default `doctor`, `factory`, `--version` |
| stderr only (success) | `search` when index missing |

<RequestExample>
```bash
LOOPANY_HOME=/tmp/brain loopany artifact create \
  --kind task --title "Follow up" --status todo --priority high
```
</RequestExample>

<ResponseExample>
```json
{
  "id": "follow-up",
  "kind": "task",
  "path": "/tmp/brain/artifacts/tasks/follow-up.md"
}
```
</ResponseExample>

## Errors and exit codes

| Error type | stderr | exit |
| --- | --- | --- |
| `WorkspaceNotFoundError` | `No loopany workspace at … Run loopany init` | `1` |
| `SchemaVersionMismatchError` | Version mismatch + migrate hint | `1` |
| `ZodError` | `Invalid input:` with `path: message` lines (not raw Zod JSON) | `1` |
| Other `Error` | `error.message` | `1` |
| `doctor` with failing checks | Human or JSON report | `1` |
| Unknown command | `Unknown command` + help hint | `1` |

Failed operations still append an audit row with `error`.

## Invocation examples

<CodeGroup>
```bash title="Development"
bun run src/cli.ts init
LOOPANY_HOME=~/loopany bun run src/cli.ts doctor
```

```bash title="Compiled binary"
bun run build
./bin/loopany init
loopany kind list
```
</CodeGroup>

```bash
# Graph + scheduling
loopany refs add --from task-a --to task-b --relation led-to
loopany trace task-a --direction forward --max-depth 3
loopany followups --due overdue

# Search pipeline
loopany reindex --no-embed
loopany search "billing refactor" --kind note --limit 5
```

## Related pages

<CardGroup>
<Card title="Artifact commands" href="/artifact-commands">
Per-subcommand flags, kind fields, body input, and Outcome rules.
</Card>
<Card title="Graph, search, and scheduling" href="/graph-search-commands">
refs/trace depth, followups modes, reindex and factory details.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`config.yaml`, `LOOPANY_HOME`, schema version guard.
</Card>
<Card title="Doctor and troubleshooting" href="/doctor-and-troubleshooting">
Check list, common errors, index recovery.
</Card>
<Card title="Schema migration" href="/schema-migration">
`loopany migrate` and v0.1→v0.2 scripts.
</Card>
<Card title="Quickstart" href="/quickstart">
First session command sequence.
</Card>
</CardGroup>

---

## 16. Artifact commands

> create/get/list/append/status/set flags, per-kind --field validation, --slug and --content-file, journal auto-management, and required ## Outcome on terminal task statuses.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/16-artifact-commands.md
- Generated: 2026-06-05T19:01:29.843Z

### Source Files

- `src/commands/artifact-create.ts`
- `src/commands/artifact-get.ts`
- `src/commands/artifact-list.ts`
- `src/commands/artifact-append.ts`
- `src/commands/artifact-status.ts`
- `src/commands/artifact-set.ts`
- `src/commands/body-input.ts`
- `test/artifact-store.test.ts`

---
title: "Artifact commands"
description: "create/get/list/append/status/set flags, per-kind --field validation, --slug and --content-file, journal auto-management, and required ## Outcome on terminal task statuses."
---

`loopany artifact` is the single namespace for every artifact kind: create, read, list, append body sections, transition status, and patch frontmatter fields. Commands delegate to `ArtifactStore` (markdown files under `$LOOPANY_HOME/artifacts/`) and the kind registry (Zod frontmatter + status machines from `kinds/*.md`). Mutating commands emit JSON on stdout; `artifact get` defaults to raw markdown.

## Command surface

| Command | Positional args | Primary flags | stdout |
|---|---|---|---|
| `artifact create` | — | `--kind`, `--slug`, `--domain`, `--content` \| `--content-file`, per-kind `--<field>` | `{ id, kind, path }` |
| `artifact get` | `<id>` | `--format md` (default) \| `json` | Markdown file or JSON object |
| `artifact list` | — | `--kind`, `--status`, `--domain`, `--contains`, per-kind filters | JSON array of `{ id, kind, path, frontmatter }` |
| `artifact append` | `<id>` | `--section`, `--content` \| `--content-file` | `{ id }` |
| `artifact status` | `<id> <new-status>` | `--reason`, `--addressed-by` | `{ id, status, reason?, addressedBy?, edgesEmitted }` |
| `artifact set` | `<id>` | `--field`, `--value` | `{ id, field, value }` |

Every flag takes exactly one value (no boolean shorthand). Kebab-case flags map to camelCase frontmatter keys (`--check-at` → `checkAt`).

<Note>
`loopany --help` shows `artifact set <id> --<field> <value>`, but the implementation requires `artifact set <id> --field <name> --value <value>`.
</Note>

## `artifact create`

Creates a new markdown artifact, validates frontmatter against the kind’s Zod schema, allocates a globally unique slug id, stamps `createdAt` / `updatedAt`, and (for non-journal kinds) best-effort appends a wiki-link into today’s journal.

<ParamField body="--kind" type="string" required>
Kind name (e.g. `task`, `signal`, `person`). Must exist in the loaded kind registry.
</ParamField>

<ParamField body="--slug" type="string">
Explicit artifact id used in `[[wiki-links]]` and graph edges. Validated (1–60 codepoints; Unicode letters/digits, `-`, `_`; no leading/trailing separators; no `..` path tricks). Must be globally unique across all kinds.
</ParamField>

<ParamField body="--domain" type="string">
Cross-kind scope tag. Not validated against a kind field spec; written as built-in frontmatter.
</ParamField>

<ParamField body="--content" type="string">
Initial body (inline). Mutually exclusive with `--content-file`.
</ParamField>

<ParamField body="--content-file" type="path | -">
File path or `-` for stdin. Preserves real newlines; preferred for multi-line bodies.
</ParamField>

### Per-kind field flags

Any flag that is not reserved is treated as a frontmatter field for the given `--kind`:

- Unknown flags → error listing valid fields for that kind (type, required, enum values, defaults).
- Types: `string`, `enum`, `date`, `number`, `bool`, `string[]` (comma-separated or JSON array for `string[]` on `artifact set`; create uses comma-split).

Reserved flags (never written as frontmatter): `--kind`, `--slug`, `--domain`, `--content`, `--content-file`.

If the kind has a status machine and `--status` is omitted, the store sets `status` to the machine’s `initial` value before validation.

<RequestExample>

```bash
loopany artifact create --kind task \
  --slug q2-cache-spike \
  --title "Investigate cache thrash" \
  --status todo \
  --priority high \
  --domain ads \
  --content-file body.md
```

</RequestExample>

<ResponseExample>

```json
{
  "id": "q2-cache-spike",
  "kind": "task",
  "path": "/Users/you/loopany/artifacts/tasks/q2-cache-spike.md"
}
```

</ResponseExample>

### Slug allocation order

When `--slug` is omitted:

1. Slugify `--title` (Unicode-aware lowercase, punctuation collapsed to `-`; collisions get `-2`, `-3`, …).
2. If title is missing or slugifies to empty → timestamp id `YYYYMMDD-HHMMSS-<3hex>`.

Explicit `--slug` always wins over a title-derived id.

### Journal rejection

`--kind journal` is rejected at the CLI:

```text
Cannot create journal artifacts directly — they are auto-managed by the store.
```

## Body input (`--content` / `--content-file`)

Shared by `artifact create` and `artifact append` via `resolveBody`:

| Source | Behavior |
|---|---|
| `--content <str>` | Inline string; rejects literal `\n` with no real newlines (shell escape footgun) |
| `--content-file <path>` | Reads UTF-8 file |
| `--content-file -` | Reads stdin (`Bun.stdin.text()`) |
| Neither (create only) | Empty body allowed |

<Warning>
Passing both `--content` and `--content-file` fails. `artifact append` requires one of them and rejects empty section bodies.
</Warning>

## Journal auto-management

On every successful `artifact create` for a non-journal kind (when the `journal` kind is registered):

1. Resolve today’s date from `createdAt` (or current date).
2. Ensure `artifacts/journal/<YYYY>/<YYYY-MM-DD>.md` exists (skeleton with `## Activity`).
3. Append `- HH:MM [[<id>]] — <title>` under `## Activity` (or `## Backfilled` when `_backfilled: true`).

Failures are logged to stderr as warnings and **do not** roll back the created artifact. Manual journal creates are blocked; direct file edits outside `## Activity` are preserved.

## `artifact get`

<ParamField body="--format" type="md | json" default="md">
`md`: raw file bytes. `json`: `{ id, kind, path, frontmatter, body, audit }` where `audit` filters `audit.jsonl` for ops `artifact.create`, `artifact.append`, `artifact.status`, `artifact.set`, `refs.add` touching this id.
</ParamField>

## `artifact list`

Starts from the smallest indexed slice (`--kind` → `byKind`, else `--status` → `byStatus`, else `--domain` → `byDomain`, else all), then intersects additional filters.

| Filter | Index path |
|---|---|
| `--kind <K>` + indexed field (e.g. `--status`, `--priority` on `task`) | Per-kind field index (`indexedFields` in kind def) |
| Other frontmatter flags | Linear scan on candidates |
| `--contains <Q>` | Case-insensitive substring in body (reads files) or string frontmatter values; applied last |

`string[]` frontmatter matches if any element equals the query value.

<RequestExample>

```bash
loopany artifact list --kind task --status running --contains "cache"
```

</RequestExample>

## `artifact append`

Appends or extends an H2 section in the artifact body (immutable pattern: no in-place body rewrite except section merge).

<ParamField body="--section" type="string" required>
Section name without `##` (e.g. `Outcome` → `## Outcome` in the file).
</ParamField>

If the section exists, new content is inserted before the next `##` heading; duplicate `## Outcome` headings are not created.

## `artifact status`

Transitions `status` through the kind’s status machine. Illegal transitions throw (e.g. `todo` → `in_review` on `task`).

<ParamField body="--reason" type="string">
Recorded in JSON stdout only. Does **not** append a `## Status` section to the body.
</ParamField>

<ParamField body="--addressed-by" type="artifact id">
Required when transitioning a `signal` to `addressed`. Emits `refs add` edge `<addressed-by> addresses <signal>` (returns `edgesEmitted: 1`). Invalid on other statuses.
</ParamField>

Kinds without a status machine reject all status changes.

### Task terminal workflow and `## Outcome`

The `task` kind definition requires `## Outcome` in the body when `status` is `done` or `failed`. That rule is documented in `kinds/task.md` and enforced by agent workflows (capture, reflect); **the CLI store does not block** `artifact status … done` without an Outcome section.

Recommended terminal sequence:

<Steps>
<Step title="Append Outcome">
```bash
loopany artifact append <task-id> --section Outcome --content "What shipped or failed, with evidence."
```
Or use `--content-file` for longer write-ups.
</Step>
<Step title="Transition status">
```bash
loopany artifact status <task-id> done --reason "one-line summary"
# or
loopany artifact status <task-id> failed --reason "blocked on X"
```
</Step>
<Step title="Verify">
```bash
loopany artifact get <task-id> --format json
```
Confirm `frontmatter.status` and `body` contains `## Outcome`.
</Step>
</Steps>

| Task status | Outcome expectation (kind contract) |
|---|---|
| `done` | `## Outcome` required (workflow) |
| `failed` | `## Outcome` required (workflow) |
| `cancelled` | `--reason` on status; no Outcome requirement in kind def |

Other kinds with documented required sections (`skill-proposal` on accept/reject, `learning` on supersede/archive) follow the same pattern: append first, status second; runtime validates frontmatter and transitions only.

## `artifact set`

Updates one non-status frontmatter field, re-validates with the kind schema, bumps `updatedAt`.

<ParamField body="--field" type="string" required>
Frontmatter key (camelCase as stored, e.g. `title`, `checkAt`, `domain`).
</ParamField>

<ParamField body="--value" type="string" required>
Coerced per field spec (`string[]` also accepts JSON array syntax).
</ParamField>

- `status` → rejected; use `artifact status`.
- Built-ins without kind specs: `domain`, `createdAt`, `updatedAt`, `_backfilled`.

## Errors and validation

| Failure | Typical message / behavior |
|---|---|
| Unknown kind | `Unknown kind: …` |
| Unknown field flag on create | Lists valid `--<field>` lines for that kind |
| Zod frontmatter | `Invalid input:` with per-field paths (not raw Zod JSON) |
| Duplicate slug | `Slug already exists: …` |
| Illegal status transition | `Illegal transition: … → … (allowed: […])` |
| Missing append body | `Missing body: pass --content … or --content-file …` |
| Literal `\n` in `--content` | Actionable shell/newline guidance |

Successful commands also append best-effort rows to `audit.jsonl` (`op` like `artifact.create`); failures may still log with `error`.

## Lifecycle (create → close)

```mermaid
stateDiagram-v2
  [*] --> todo: artifact create --kind task
  todo --> running: artifact status running
  running --> in_review: artifact status in_review
  in_review --> done: append Outcome then status done
  running --> failed: append Outcome then status failed
  todo --> cancelled: artifact status cancelled
```

```text
  artifact create ──► ArtifactStore.create ──► artifacts/<dir>/<id>.md
        │                      │
        │                      └──► journal auto-link (best-effort)
        │
  artifact append ──► appendSection (body H2)
  artifact status ──► setStatus (frontmatter only)
  artifact set    ──► setField (non-status FM)
```

## Related pages

<CardGroup>
<Card title="Kinds and validation" href="/kinds-and-validation">
Markdown kind defs, Zod schemas, status machines, and slug rules behind `--field` flags.
</Card>
<Card title="Artifacts and workspace" href="/artifacts-and-workspace">
On-disk paths, v0.2 slug-as-id layout, and `$LOOPANY_HOME` structure.
</Card>
<Card title="Artifact lifecycle example" href="/artifact-lifecycle-example">
End-to-end signal → task → Outcome → done with graph edges.
</Card>
<Card title="Capture workflow" href="/capture-workflow">
When to append Outcome before closing tasks in agent sessions.
</Card>
<Card title="CLI reference" href="/cli-reference">
Full command tree, JSON conventions, and adjacent graph/search commands.
</Card>
</CardGroup>

---

## 17. Graph, search, and scheduling

> refs add/query, trace BFS, followups --due, hybrid search/reindex (--no-embed), factory UI (--port/--no-open), and audit.jsonl side effects.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/17-graph-search-and-scheduling.md
- Generated: 2026-06-05T19:01:45.746Z

### Source Files

- `src/commands/refs.ts`
- `src/commands/trace.ts`
- `src/commands/followups.ts`
- `src/commands/search.ts`
- `src/commands/reindex.ts`
- `src/core/search-store.ts`
- `src/commands/factory.ts`
- `src/ui/server.ts`

---
title: "Graph, search, and scheduling"
description: "refs add/query, trace BFS, followups --due, hybrid search/reindex (--no-embed), factory UI (--port/--no-open), and audit.jsonl side effects."
---

`loopany refs`, `trace`, `followups`, `search`, `reindex`, and `factory` operate on three derived views of `$LOOPANY_HOME`: append-only `references.jsonl` (explicit edges), an in-memory `ArtifactIndex` rebuilt each invocation (explicit + implicit edges, `checkAt` scheduling), and optional `search.db` (FTS5 + embeddings). Every successful or failed CLI run appends one row to `audit.jsonl`.

```mermaid
flowchart LR
  subgraph storage["$LOOPANY_HOME"]
    A["artifacts/*.md"]
    R["references.jsonl"]
    S["search.db"]
    AU["audit.jsonl"]
  end
  subgraph runtime["Per CLI invocation"]
    IDX["ArtifactIndex.build"]
    REFS["refs / trace"]
    FU["followups"]
    SR["SearchStore"]
  end
  A --> IDX
  R --> IDX
  IDX --> REFS
  IDX --> FU
  A --> SR
  SR --> S
  CLI["src/cli.ts"] --> AU
  REFS --> CLI
  FU --> CLI
  SR --> CLI
```

## Command overview

| Command | Mutates workspace | Primary output |
|---------|-------------------|----------------|
| `refs add` | Appends to `references.jsonl` | Single `Edge` JSON |
| `refs <id>` | Read-only | `Edge[]` JSON |
| `trace <id>` | Read-only | `{ root, nodes, edges }` JSON |
| `followups` | Read-only | `ArtifactMeta[]` JSON |
| `reindex` | Writes `search.db` | `{ indexed, skipped, removed, embedder }` JSON |
| `search <query>` | Read-only | `SearchResult[]` JSON (empty if no index) |
| `factory` | Read-only UI; `POST /api/open` spawns editor | Blocks until Ctrl-C |

All JSON commands write pretty-printed JSON to stdout. Graph and scheduling commands bootstrap the workspace and rebuild `ArtifactIndex` on each run (O(n) over artifacts).

## Reference graph: `refs`

### Add an explicit edge

```bash
loopany refs add --from <id> --to <id> --relation <verb>
```

<ParamField body="--from" type="string" required>
Source artifact id (slug).
</ParamField>

<ParamField body="--to" type="string" required>
Target artifact id.
</ParamField>

<ParamField body="--relation" type="string" required>
Open-registry relation verb (for example `led-to`, `addresses`, `mentions`).
</ParamField>

Appends one JSON line to `references.jsonl`:

```json
{"ts":"2026-06-05T12:00:00.000Z","from":"sig-q2","to":"tsk-ship","relation":"led-to","actor":"cli"}
```

Reverse edges are not stored; `ArtifactIndex` builds `refsIn` / `refsOut` maps at load time.

<RequestExample>

```bash
loopany refs add --from sig-q2 --to tsk-ship --relation led-to
```

</RequestExample>

<ResponseExample>

```json
{
  "ts": "2026-06-05T12:00:00.000Z",
  "from": "sig-q2",
  "to": "tsk-ship",
  "relation": "led-to",
  "actor": "cli"
}
```

</ResponseExample>

### Query neighbors (BFS)

```bash
loopany refs <id> [--direction in|out|both] [--relation R] [--depth N] [--domain D]
```

| Flag | Default | Behavior |
|------|---------|----------|
| `--direction` | `out` | Follow outgoing, incoming, or both edge sets per hop |
| `--depth` | `1` | Positive integer; N hops from the start id |
| `--relation` | (all) | Keep only edges matching this verb |
| `--domain` | (all) | Both endpoint artifacts must have `frontmatter.domain` equal to D |

Traversal is BFS over nodes. Each unique edge (`from|to|relation|ts`) is returned once even if reachable on multiple paths. Visited nodes are not re-expanded, but all edges encountered along the frontier are collected.

<Note>
Implicit edges appear in `refs` queries but are never written to `references.jsonl`. At index build time, `mentions` edges are synthesized from `frontmatter.mentions[]` and body `[[id]]` wiki links (`actor`: `frontmatter` or `body`, `implicit: true`). Updating frontmatter or body changes implicit edges on the next CLI run.
</Note>

## Causal lineage: `trace`

```bash
loopany trace <id> [--direction forward|backward|both] [--relations csv] [--max-depth N]
```

`trace` walks a **subset** of relations for lineage, not the full graph. Default predicates: `led-to`, `addresses`, `supersedes`, `follows-up`, `cites`. `mentions` is excluded (soft pointer, not lineage). `caused-by` is also excluded; use `--direction backward` on `led-to` edges to reach upstream work.

| Flag | Default | Behavior |
|------|---------|----------|
| `--direction` | `both` | `forward` follows `refsOut`; `backward` follows `refsIn` |
| `--relations` | causal set above | Comma-separated override; must list at least one verb |
| `--max-depth` | unlimited | Positive integer cap per direction |

<ResponseField name="root" type="string">
Start artifact id.
</ResponseField>

<ResponseField name="nodes" type="TraceNode[]">
Artifact metadata plus signed `distance`: negative = backward (causes), `0` = root, positive = forward (effects). Sorted by `distance`, then `id`. Dangling edge targets are dropped.
</ResponseField>

<ResponseField name="edges" type="Edge[]">
Edges used in the walk (deduped by `from|to|relation|ts`).
</ResponseField>

Cycle-safe: each node is assigned at most one distance; walks terminate on revisits.

<Warning>
`trace` requires the root id to exist. Unknown ids exit with `No artifact with id: …`.
</Warning>

<RequestExample>

```bash
loopany trace tsk-ship --direction both --max-depth 3
```

</RequestExample>

## Scheduling: `followups`

```bash
loopany followups [--due today|overdue|next-7d] [--include-done true] [--domain D]
```

Selects artifacts whose `checkAt` frontmatter (ISO date string) is on or before a cutoff:

| `--due` | Cutoff | Extra filter |
|---------|--------|--------------|
| `today` (default) | Today (UTC date portion) | None |
| `next-7d` | Today + 7 days | None |
| `overdue` | Today | Keeps only `checkAt` date **strictly before** today |

Unless `--include-done true`, results exclude artifacts in a **terminal** status: the kind’s status machine has no outgoing transitions from the current status (for example `done`, `cancelled`, `failed` on `task`). Kinds without a status machine are never filtered this way.

Output is JSON array of `ArtifactMeta` objects (`id`, `kind`, `path`, `frontmatter`) — same shape as `artifact list`, filtered by schedule.

<Steps>

<Step title="Daily review">
Run `loopany followups --due today` (optionally `--domain crm`) to list actionable `checkAt` items.
</Step>

<Step title="Overdue sweep">
Run `loopany followups --due overdue` for items past their date that are still non-terminal.
</Step>

<Step title="Weekly horizon">
Run `loopany followups --due next-7d` to see the next week’s scheduled reviews.
</Step>

</Steps>

## Hybrid search

### Rebuild index: `reindex`

```bash
loopany reindex [--force] [--no-embed]
```

| Flag | Effect |
|------|--------|
| `--force` | Deletes `search.db` and rebuilds FTS5 + chunk tables from scratch |
| `--no-embed` | Uses `NoopEmbedder` — FTS5-only index (no ONNX download; used in CI) |

Incremental by default: compares each artifact file’s mtime to `artifact_mtimes` in `search.db` and skips unchanged files. After scanning disk, removes index rows for artifact ids no longer present.

<Info>
Artifact create/append/status does **not** auto-reindex in v1. Run `reindex` after bulk edits or when you want fresh embeddings.
</Info>

<ResponseExample>

```json
{
  "indexed": 12,
  "skipped": 45,
  "removed": 1,
  "embedder": "transformers"
}
```

</ResponseExample>

`embedder` is `transformers` when `Xenova/all-MiniLM-L6-v2` loads, otherwise `noop` (keyword-only search still works).

Indexing pipeline (`SearchStore`):

- Chunks: optional title chunk + body split at `##` / `###` headings
- Keyword: SQLite FTS5 (`chunks_fts`) with BM25 rank
- Semantic: 384-dim embeddings when embedder is available; cosine similarity ≥ `0.3`
- Fusion: Reciprocal Rank Fusion (`k = 60`) across keyword and semantic ranked lists; best chunk per artifact wins

Derived file: `$LOOPANY_HOME/search.db` (safe to delete; markdown artifacts remain canonical).

### Query: `search`

```bash
loopany search <query> [--kind K] [--domain D] [--status S] [--limit N]
```

| Flag | Default |
|------|---------|
| `--limit` | `10` |

If `search.db` is missing, stdout is `[]`, stderr prints `no search index found — run loopany reindex first.`, exit code `0` (scripts can probe without failing).

<ResponseField name="artifactId" type="string">
Matched artifact id.
</ResponseField>

<ResponseField name="snippet" type="string">
Truncated chunk text (~200 chars).
</ResponseField>

<ResponseField name="score" type="number">
RRF fused score (higher is better).
</ResponseField>

Additional fields: `kind`, `domain`, `status`, `path`, `section`.

<RequestExample>

```bash
loopany reindex
loopany search "billing refactor" --kind note --limit 5
```

</RequestExample>

<Tip>
Use `loopany reindex --no-embed` in automation or offline environments; use a full `reindex` (no flag) when semantic recall matters and the Hugging Face model can download to `HF_HOME`.
</Tip>

## Factory UI: `factory`

```bash
loopany factory [--port N] [--host H] [--no-open]
```

| Flag | Default |
|------|---------|
| `--port` | `4242` |
| `--host` | `127.0.0.1` |
| `--no-open` | (browser opens via `open` / `xdg-open` / `start`) |

Starts a local Bun HTTP server (`src/ui/server.ts`) serving a read-only Kaplay pixel view. Binds to loopback only — no auth, no CORS.

| Route | Method | Purpose |
|-------|--------|---------|
| `/` | GET | Factory HTML + game |
| `/api/graph` | GET | Workspace graph JSON from `buildGraph()` |
| `/api/open` | POST | `{ "id": "<artifact>" }` → opens artifact path in editor |

Graph payload includes `nodes`, `edges` (explicit + implicit), `lanes` (kind ordering), `domains`, `enabledDomains`, `workspace`. Nodes carry card fields and a short body preview, not full markdown.

Editor resolution for `/api/open`:

1. `$LOOPANY_EDITOR` (shell-split command, e.g. `cursor` or `code -n`)
2. Platform default: `open` (macOS), `xdg-open` (Linux), `start` (Windows)

Process runs until SIGINT/SIGTERM; then the server stops.

## `audit.jsonl` side effects

Every CLI invocation (including read-only graph/search queries) triggers a best-effort append to `$LOOPANY_HOME/audit.jsonl` after the command finishes. Audit failures are silent and never change the command exit code.

Row shape:

```json
{
  "ts": "2026-06-05T12:00:00.000Z",
  "op": "refs.add",
  "actor": "cli",
  "duration_ms": 42,
  "from": "sig-q2",
  "to": "tsk-ship",
  "relation": "led-to"
}
```

`op` is the command key with spaces as dots (`refs`, `refs.add`, `trace`, `followups`, `search`, `reindex`, `factory`). Failed runs add `"error": "<message>"`. Large fields (artifact bodies) are never logged.

| Command | Typical audit fields |
|---------|---------------------|
| `refs add` | `from`, `to`, `relation` |
| `refs` | `count` |
| `trace` | `nodes`, `edges` |
| `followups` | `count` |
| `search` | `count` |
| `reindex` | `indexed`, `skipped`, `removed`, `embedder` |
| `factory` | `op` only (duration spans entire session) |

`artifact get <id> --format json` attaches filtered audit history for that id (`artifact.create`, `artifact.append`, `artifact.status`, `artifact.set`, `refs.add` where `id`, `from`, or `to` matches).

<Check>
Verify audit growth: run a command, then `tail -1 $LOOPANY_HOME/audit.jsonl | jq .op`.
</Check>

## `refs` vs `trace`

| | `refs <id>` | `trace <id>` |
|---|-------------|--------------|
| Relations | All (unless `--relation`) | Default causal set; `mentions` excluded |
| Implicit edges | Included | Included if relation filter allows |
| Output | Flat `Edge[]` | Nodes with signed `distance` + edges |
| Use case | Neighborhood / degree-N graph | Cause → root → effect narrative |

## Errors and recovery

| Symptom | Cause | Recovery |
|---------|-------|----------|
| `refs add requires --from, --to, --relation` | Missing flags | All flags require values (`--relation led-to`) |
| `Invalid --depth` / `--max-depth` | Non-positive integer | Pass `1` or greater |
| `No artifact with id` on `trace` | Unknown root | Confirm id via `artifact list` |
| Empty `search` + stderr hint | No `search.db` | `loopany reindex` |
| `embedder: "noop"` after reindex | Model load failed | Check stderr during reindex; FTS-only still works |
| Stale search hits | Index not refreshed | `reindex` (or `reindex --force` after model change) |

## Related pages

<CardGroup>

<Card title="Reference graph" href="/reference-graph">
Append-only edges, implicit mentions, relation conventions, and wiki-link rules.
</Card>

<Card title="CLI reference" href="/cli-reference">
Full command surface and JSON stdout conventions.
</Card>

<Card title="Artifacts and workspace" href="/artifacts-and-workspace">
`references.jsonl`, `search.db`, `audit.jsonl`, and on-disk layout.
</Card>

<Card title="Periodic review" href="/periodic-review">
Cadence for `followups --due today` and overdue sweeps.
</Card>

<Card title="Doctor and troubleshooting" href="/doctor-and-troubleshooting">
Index health, dangling refs, and factory/reindex recovery.
</Card>

</CardGroup>

---

## 18. Configuration reference

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

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/18-configuration-reference.md
- Generated: 2026-06-05T19:02:59.852Z

### 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>

---

## 19. Artifact lifecycle example

> End-to-end recipe: signal → task with led-to/addresses edges, status transitions, brief output, and journal linkage—mirroring test/scenario.e2e and skill-regression flows.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/19-artifact-lifecycle-example.md
- Generated: 2026-06-05T19:02:46.889Z

### Source Files

- `test/scenario.e2e.test.ts`
- `test/skill-regression.sh`
- `skills/loopany-core/kinds/signal.md`
- `skills/loopany-core/kinds/task.md`
- `skills/loopany-core/conventions/relations.md`
- `src/commands/artifact-status.ts`
- `src/core/artifact-store.ts`

---
title: "Artifact lifecycle example"
description: "End-to-end recipe: signal → task with led-to/addresses edges, status transitions, brief output, and journal linkage—mirroring test/scenario.e2e and skill-regression flows."
---

The canonical end-to-end path is exercised by `test/scenario.e2e.test.ts`: record a `signal`, create a `person` and `task`, wire `led-to` and `mentions` edges, pick up work via `followups`, transition the task through `running` → `in_review` → `done` with a `## Outcome`, then verify graph queries. Every non-`journal` create also appends a wiki-link into today's auto-managed journal under `artifacts/journal/<YYYY>/<YYYY-MM-DD>.md`.

## Prerequisites

| Requirement | Notes |
|---|---|
| Workspace | `loopany init` with `$LOOPANY_HOME` set (tests use an isolated temp dir) |
| CLI | `bun run src/cli.ts` or a linked `loopany` binary |
| Kinds | Bundled under `$LOOPANY_HOME/kinds/` after init (`signal`, `task`, `person`, `brief`, `journal`, …) |

<Note>
v0.2 IDs are globally unique slugs (no kind prefix). Title-derived slugs are the default (`Follow up with Alice` → `follow-up-with-alice`); explicit slugs use `--slug` (required for `person`).
</Note>

## Canonical scenario: signal → task → done

This mirrors the Alice contract follow-up in `test/scenario.e2e.test.ts`.

<Steps>
<Step title="Initialize workspace">

```bash
export LOOPANY_HOME=~/loopany   # or a temp path for experiments
loopany init
```

</Step>

<Step title="Record the inbound signal">

```bash
loopany artifact create \
  --kind signal \
  --title "User wants to follow up with Alice about contract"
```

Stdout is JSON with `id`, `kind`, and `path`. The store stamps `status: open` (signal initial state) and appends `[[<id>]]` to today's journal `## Activity` section.

</Step>

<Step title="Create the person entity">

```bash
loopany artifact create \
  --kind person \
  --slug alice-chen \
  --name "Alice Chen" \
  --aliases "alice,a.chen"
```

File lands at `artifacts/people/alice-chen.md`. The slug is the durable ID for `[[alice-chen]]` citations.

</Step>

<Step title="Create the task with scheduling and mentions">

```bash
loopany artifact create \
  --kind task \
  --title "Follow up with Alice on contract" \
  --status todo \
  --priority high \
  --check-at 2020-01-01 \
  --mentions alice-chen
```

`checkAt` is indexed for `loopany followups`. In the e2e test the date is intentionally in the past so `--due today` returns the task immediately.

</Step>

<Step title="Wire reference edges">

Weak causal link (signal produced the task):

```bash
loopany refs add --from <signal-id> --to <task-id> --relation led-to
```

Stakeholder mention (task talks about the person):

```bash
loopany refs add --from <task-id> --to alice-chen --relation mentions
```

`mentions` can also be satisfied by frontmatter `--mentions` or body `[[alice-chen]]`; all three emit the same semantic edge (implicit edges from frontmatter/body vs persisted edges from `refs add`).

</Step>

<Step title="Pick up due work">

```bash
loopany followups --due today
```

Returns JSON metadata for artifacts whose `checkAt` is on or before the cutoff. Terminal statuses (`done`, `cancelled`, `failed` for tasks) are excluded unless `--include-done true`.

</Step>

<Step title="Run the task status machine">

```bash
loopany artifact status <task-id> running
loopany artifact status <task-id> in_review
```

Illegal jumps are rejected by the store (e.g. `todo` → `in_review` fails with a transition error).

</Step>

<Step title="Close with Outcome and done">

```bash
loopany artifact append <task-id> \
  --section Outcome \
  --content "Alice signed today; contract effective 2026-04-29."

loopany artifact status <task-id> done --reason signed
```

The kind playbook requires `## Outcome` before `done` or `failed`; the CLI does not block `done` without that section today—agents and capture skills treat missing Outcomes as a quality failure. `--reason` updates frontmatter only; it does not append a body section.

</Step>

<Step title="Verify end state">

```bash
loopany artifact get <task-id> --format json
loopany refs <signal-id>
loopany refs alice-chen --direction in
loopany artifact list --kind task --status done
```

Expect `frontmatter.status: done`, body containing `## Outcome`, outbound `led-to` from the signal, and inbound `mentions` on the person.

</Step>
</Steps>

### Task and signal status machines

```mermaid
stateDiagram-v2
  direction LR
  state "signal" as sig {
    [*] --> open
    open --> addressed
    open --> dismissed
    addressed --> open
    dismissed --> open
  }
  state "task" as tsk {
    [*] --> todo
    todo --> running
    todo --> done
    todo --> cancelled
    running --> in_review
    running --> done
    running --> failed
    running --> cancelled
    in_review --> done
    in_review --> failed
    in_review --> cancelled
  }
```

## Relation verbs in this flow

| Verb | Direction in this recipe | Meaning |
|---|---|---|
| `led-to` | `signal` → `task` | Weak causal: the observation eventually produced the work item |
| `mentions` | `task` → `person` | Soft reference to an entity involved in the work |
| `addresses` | `task` → `signal` | Strong responsibility: the task resolves the observation |

<Warning>
Do not write inverse edges (`task` → `signal` with `led-to`). Query `--direction in` on the signal instead.
</Warning>

### Closing the signal with `addresses`

After the task ships, mark the signal `addressed` and emit the canonical `addresses` edge in one call:

```bash
loopany artifact status <signal-id> addressed --addressed-by <task-id>
```

`artifact-status.ts` appends `<task-id> addresses <signal-id>` to `references.jsonl` and flips `status` atomically. Transitioning to `addressed` without `--addressed-by` errors. This pattern is covered in `test/cli.e2e.test.ts` and is what `test/skill-regression.sh` scenario 9 expects when upgrading a recurring signal to a task.

`led-to` during intake plus `addresses` on close is the full pair: causality while open, responsibility when finished.

## Journal linkage (automatic)

On every `artifact create` except `journal`, `ArtifactStore` calls `appendToTodayJournal`:

- **Path:** `artifacts/journal/<YYYY>/<YYYY-MM-DD>.md` (`slugLayout: year`, id = date string)
- **Section:** `## Activity` (or `## Backfilled` when `_backfilled: true`)
- **Line format:** `HH:MM [[slug]] — title` (time omitted when unavailable)

Manual `loopany artifact create --kind journal` is rejected at the CLI; the store is the sole writer of journal files.

Each journal wiki-link also yields an implicit `mentions` edge from the journal id to the new artifact. When debugging graph queries, filter `implicit: true` edges if you only care about explicit `refs add` lines.

**Read today's index two ways:**

1. Open the journal markdown file and scan `## Activity`.
2. `loopany refs <YYYY-MM-DD> --direction out` (same set via graph).

For user-facing narrative, create a `brief` (see below)—not a hand-edited journal.

## Extension: brief output after completion

A `brief` is the agent's point-in-time summary back to the user (`skills/loopany-core/kinds/brief.md`). It has no status machine; cite sources so the summary is drillable.

```bash
loopany artifact create \
  --kind brief \
  --title "Morning briefing" \
  --for-date 2026-06-05 \
  --mentions "<task-id>,alice-chen" \
  --content "$(cat <<'EOF'
## What's due today
## What changed since last briefing
- [[<task-id>]] closed: Alice signed; contract effective 2026-04-29.
## Open threads
## Suggested next moves
EOF
)"
```

Persist lineage edges:

```bash
loopany refs add --from <task-id> --to <brief-id> --relation led-to
loopany refs add --from <brief-id> --to <task-id> --relation cites
```

The brief auto-links into the same day's journal like any other kind. To replace a briefing, create a new brief and `supersedes` the old—never rewrite cited briefs in place.

## Skill-regression cross-check

`test/skill-regression.sh` runs Claude with loopany skills installed and asserts workspace shape:

| Scenario | Behavior checked |
|---|---|
| 1 | Natural-language signal creation |
| 2 | Task creation + flip to `running` |
| 9 | Signal upgraded to task; signal status `addressed` |
| 5 | Shipped PR captured as `task` with Outcome-quality work |

Run locally (requires `claude` CLI and API key):

```bash
./test/skill-regression.sh --dry-run   # inspect prompts only
./test/skill-regression.sh 9           # signal → task upgrade only
```

## End-to-end sequence

```mermaid
sequenceDiagram
  participant User
  participant CLI as loopany CLI
  participant Store as ArtifactStore
  participant Journal as journal YYYY-MM-DD
  participant Refs as references.jsonl

  User->>CLI: artifact create signal
  CLI->>Store: create(signal)
  Store->>Journal: append Activity [[signal-id]]
  User->>CLI: artifact create person / task
  Store->>Journal: append Activity lines
  User->>CLI: refs add led-to / mentions
  CLI->>Refs: append edges
  User->>CLI: followups --due today
  User->>CLI: artifact status running / in_review
  User->>CLI: artifact append Outcome
  User->>CLI: artifact status done
  User->>CLI: artifact status signal addressed --addressed-by task
  CLI->>Refs: task addresses signal
  opt Brief
    User->>CLI: artifact create brief + cites/led-to
    Store->>Journal: append [[brief-id]]
  end
```

## Verification commands

| Check | Command |
|---|---|
| E2E scenario | `LOOPANY_HOME=$(mktemp -d) bun test test/scenario.e2e.test.ts` |
| Addressed edge | `bun test test/cli.e2e.test.ts -t "flips signal to addressed"` |
| Followups filter | `bun test test/cli.e2e.test.ts -t "loopany followups"` |
| Full suite | `bun run test` |

<Check>
A healthy lifecycle leaves: (1) markdown files under `artifacts/signals/`, `artifacts/tasks/`, `artifacts/people/`; (2) persisted edges in `references.jsonl`; (3) today's journal with wiki-linked Activity lines; (4) terminal task with `## Outcome` in the body.
</Check>

## Common failures

| Symptom | Cause | Fix |
|---|---|---|
| `Illegal transition` | Status jump not in kind machine | Follow `todo` → `running` → `in_review` → `done` for tasks |
| `addressed-by` error | `addressed` without responsible artifact | Pass `--addressed-by <task-id>` |
| Signal not in followups | `followups` only reads `checkAt` | Tasks use `--check-at`; signals have no `checkAt` field |
| Extra graph edges | Auto-journal `mentions` | Filter implicit edges in JSON output |
| `Cannot create journal` | Manual journal create | Create any other kind; journal is auto-managed |

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
First session: init, mission, task, one reference edge, doctor.
</Card>
<Card title="Reference graph" href="/reference-graph">
Relation verbs, implicit mentions, refs/trace queries.
</Card>
<Card title="Artifact commands" href="/artifact-commands">
create, append, status, set, and Outcome conventions.
</Card>
<Card title="Capture workflow" href="/capture-workflow">
End-of-task capture gate and event→kind routing.
</Card>
<Card title="Self-improvement example" href="/self-improvement-example">
Three done tasks with outcomes → reflect → skill-proposal.
</Card>
<Card title="Build and test" href="/build-and-test">
Unit/e2e tests and skill-regression.sh prerequisites.
</Card>
</CardGroup>

---

## 20. Self-improvement example

> Recipe: three done tasks with outcomes → reflect writes learning + pending skill-proposal → user accepts → skill file diff and proposal Outcome recorded.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/20-self-improvement-example.md
- Generated: 2026-06-05T19:02:51.281Z

### Source Files

- `skills/loopany-reflect/SKILL.md`
- `skills/loopany-core/kinds/task.md`
- `skills/loopany-core/kinds/learning.md`
- `skills/loopany-core/kinds/skill-proposal.md`
- `test/skill-regression.sh`
- `src/commands/artifact-status.ts`

---
title: "Self-improvement example"
description: "Recipe: three done tasks with outcomes → reflect writes learning + pending skill-proposal → user accepts → skill file diff and proposal Outcome recorded."
---

The self-improvement loop in loopany is a skill-driven workflow (`loopany-reflect`) over three artifact kinds: `task` (evidence via `## Outcome`), `learning` (belief), and `skill-proposal` (pending skill edit). The CLI enforces status machines and frontmatter schemas; pattern thresholds, body sections, and skill-file edits are governed by kind playbooks and the reflect skill. Agents never patch `SKILL.md` files directly—acceptance goes through a proposal artifact plus git.

## Prerequisites

| Requirement | Detail |
|-------------|--------|
| Workspace | `loopany init` with `$LOOPANY_HOME` set |
| Mission | Active `mission` artifact (resolver bootstrap blocks other work without one) |
| Skills | `loopany-reflect` installed and routable from `loopany-resolver` |
| Evidence | ≥ 3 `task` artifacts in `done` with distinct, comparable `## Outcome` sections |

<Note>
`test/skill-regression.sh` scenario 8 asserts reflect must **not** create a `learning` when fewer than three completed tasks exist. Reflect after every single task is an anti-pattern.
</Note>

## Scenario

Three metric-related tasks closed without a `## Before` baseline. Outcomes report numbers but lack a falsifiable before/after. Reflect should generalize that pattern into a `learning`, then a `skill-proposal` that updates the task kind playbook. A human (or delegated agent) accepts the proposal, applies the skill diff, records `## Outcome` on the proposal, and commits.

```text
$LOOPANY_HOME/
  artifacts/tasks/<task-slug>.md     ×3  (status: done, ## Outcome)
  artifacts/learnings/<lrn-slug>.md     (status: active, evidence: tasks)
  artifacts/skill-proposals/<spr-slug>.md (status: pending → accepted)
  references.jsonl                      (mentions / led-to edges)
  skills/...                            (repo checkout — edit target on accept)
```

## Phase 1 — Close three tasks with outcomes

Create and complete three tasks that share the same failure mode (metric work without baseline). Append `## Outcome` **before** flipping to `done` (kind playbook requirement; the CLI does not block `done` without that section).

<Steps>
<Step title="Create first task">

```bash
loopany artifact create --kind task \
  --slug dashboard-lcp-march \
  --title "Improve dashboard LCP" \
  --status todo \
  --content "$(cat <<'EOF'
## Plan
Ship lazy-loading for the heaviest chart widgets.
EOF
)"
```

</Step>
<Step title="Work, append Outcome, mark done">

```bash
loopany artifact status dashboard-lcp-march running
loopany artifact append dashboard-lcp-march --section Outcome --content "$(cat <<'EOF'
LCP improved from ~4.2s to ~2.8s in staging after lazy-loading charts.
No ## Before snapshot; cannot verify production baseline or control for traffic mix.
EOF
)"
loopany artifact status dashboard-lcp-march done --reason "shipped lazy-load"
```

</Step>
<Step title="Repeat for two more tasks">

Use distinct slugs with the same structural gap (e.g. `api-p99-latency-april`, `checkout-conversion-may`). Each `## Outcome` should state what moved and explicitly note the missing baseline.

</Step>
<Step title="Verify evidence pool">

```bash
loopany artifact list --kind task --status done
```

Expect ≥ 3 items. Confirm each body contains `## Outcome` via `loopany artifact get <slug>`.

</Step>
</Steps>

Terminal task statuses and required body sections are defined in the bundled `task` kind; `artifact status` only validates the status machine (`todo` → `running` → `done`, etc.).

## Phase 2 — Reflect: learning + pending proposal

Trigger reflect when the user asks or when ≥ 3 tasks recently reached `done` (not after every task).

<Steps>
<Step title="Gather and filter evidence">

```bash
loopany artifact list --kind task --status done
loopany artifact list --kind learning --status active
loopany artifact list --kind skill-proposal --status rejected
```

Subtract task slugs already listed in `evidence` on active learnings or non-rejected proposals. Default time window ≈ one week (`createdAt`, newest first).

</Step>
<Step title="Apply pattern threshold">

| Pattern | Threshold |
|---------|-----------|
| Same class of outcome | ≥ 3 tasks |
| Belief refuted | ≥ 2 contradicting outcomes |
| Belief needs caveat | ≥ 2 tasks |
| Dismissed signal recurs | ≥ 3 dismissals over ≥ 2 weeks |

Here: ≥ 3 tasks whose Outcomes lack `## Before` on metric work → valid pattern.

</Step>
<Step title="Write learning artifact">

```bash
loopany artifact create --kind learning \
  --slug metric-outcomes-need-before-2026 \
  --title "Metric task outcomes are unfalsifiable without a ## Before baseline" \
  --evidence "dashboard-lcp-march,api-p99-latency-april,checkout-conversion-may" \
  --mentions "<your-mission-slug>" \
  --check-at 2026-09-01 \
  --content "$(cat <<'EOF'
## Observation
Three recent metric tasks reported deltas without reproducible baselines.

## Evidence
- dashboard-lcp-march — "LCP improved… No ## Before snapshot…"
- api-p99-latency-april — "p99 down 18%… no baseline captured"
- checkout-conversion-may — "conversion +0.4pp… traffic mix uncontrolled"

## Scope
Applies to tasks measuring product metrics (latency, conversion, error rates).
Does not apply to pure refactors or docs-only work.

## Check-at
On 2026-09-01: count done metric tasks; do ≥ 80% include ## Before?
EOF
)"
```

`evidence` must cite ≥ 2 artifact IDs; use `--slug` on learnings because they are cited often.

</Step>
<Step title="Write skill-proposal (optional but typical here)">

Only when the learning implies a concrete skill edit:

```bash
loopany artifact create --kind skill-proposal \
  --slug require-before-on-metric-tasks \
  --title "Require ## Before on metric tasks in task playbook" \
  --target-skill skills/loopany-core/kinds/task.md \
  --change-type modify \
  --evidence "metric-outcomes-need-before-2026" \
  --mentions "metric-outcomes-need-before-2026" \
  --check-at 2026-09-01 \
  --content "$(cat <<'EOF'
## Motivation
Learning [[metric-outcomes-need-before-2026]]: three metric tasks closed without baselines.

## Proposed change
**File:** skills/loopany-core/kinds/task.md
**Intent:** Under `## Playbook` / `### Body`, elevate `## Before` from "strongly recommended" to **required** when the task title or Outcome references a measurable metric (LCP, p99, conversion, error rate).
**Location:** After the Outcome bullet list, add a short gate: "Metric tasks: append ## Before before status done."

## Expected effect
- Short term: agents pause to capture baseline screenshots or query snapshots.
- Long term: reflect can falsify beliefs from outcomes; fewer duplicate learnings.

## Check-at
2026-09-01 — sample 10 done metric tasks; measure ## Before presence rate.
EOF
)"
```

Default status is `pending`. Frontmatter uses camelCase flags on the CLI (`--target-skill`, `--change-type`, `--check-at`).

</Step>
<Step title="Verify evidence chain">

```bash
loopany trace require-before-on-metric-tasks --direction backward
```

Expect backward reachability to the learning and cited tasks (via `mentions` / `evidence` frontmatter and explicit `refs` if added).

</Step>
</Steps>

```mermaid
stateDiagram-v2
  [*] --> todo: task created
  todo --> running: artifact status
  running --> done: Outcome appended
  done --> [*]: evidence for reflect

  [*] --> active: learning created
  active --> superseded: new learning supersedes

  [*] --> pending: skill-proposal created
  pending --> accepted: user accept + Outcome
  pending --> rejected: user reject + Outcome
```

## Phase 3 — Accept proposal and apply skill diff

Acceptance is human-gated (`agent proposes, human accepts`). The reflect skill's **Proposal Apply** mode performs the edit.

<Steps>
<Step title="List pending proposals">

```bash
loopany artifact list --kind skill-proposal --status pending
```

</Step>
<Step title="Read proposal, learning, and target file">

```bash
loopany artifact get require-before-on-metric-tasks
loopany refs require-before-on-metric-tasks --direction out --relation mentions
loopany artifact get metric-outcomes-need-before-2026
```

Read `targetSkill` from proposal frontmatter (e.g. `skills/loopany-core/kinds/task.md` in the repo checkout).

</Step>
<Step title="Apply only the described change">

Edit the target file faithfully—scope is bounded by `## Proposed change`. Do not edit skills outside the proposal contract.

</Step>
<Step title="Record Outcome and flip status">

```bash
loopany artifact append require-before-on-metric-tasks --section Outcome --content "$(cat <<'EOF'
Added metric-task gate under task.md Playbook § Body: ## Before required before done when Outcome cites measurable metrics.
Interpretation: strengthens capture quality; does not auto-enforce at CLI layer.
EOF
)"
loopany artifact status require-before-on-metric-tasks accepted \
  --reason "playbook updated per proposal"
```

`skill-proposal` kind requires `## Outcome` on both `accepted` and `rejected` terminal statuses.

</Step>
<Step title="Git commit target + proposal">

```bash
git add skills/loopany-core/kinds/task.md
git add "$LOOPANY_HOME/artifacts/skill-proposals/require-before-on-metric-tasks.md"
git commit -m "accept skill-proposal: require ## Before on metric tasks"
```

</Step>
</Steps>

```mermaid
sequenceDiagram
  participant User
  participant Reflect as loopany-reflect
  participant CLI as loopany CLI
  participant WS as $LOOPANY_HOME
  participant Git as git

  User->>Reflect: reflect / accept proposal
  Reflect->>CLI: artifact list --kind task --status done
  CLI->>WS: read tasks + Outcomes
  Reflect->>CLI: artifact create --kind learning
  Reflect->>CLI: artifact create --kind skill-proposal
  CLI->>WS: pending proposal
  User->>Reflect: accept require-before-on-metric-tasks
  Reflect->>Reflect: edit targetSkill file
  Reflect->>CLI: artifact append Outcome
  Reflect->>CLI: artifact status accepted
  Reflect->>Git: commit skill + proposal artifact
```

### Reject path (contrast)

```bash
loopany artifact append <proposal-slug> --section Outcome --content "Rejected: change belongs in capture skill, not task kind."
loopany artifact status <proposal-slug> rejected --reason "wrong target"
```

Rejected proposals are listed on the next reflect pass so the same rule is not re-suggested.

## Verification checklist

| Check | Command / signal |
|-------|------------------|
| Three done tasks | `loopany artifact list --kind task --status done` → count ≥ 3 |
| Outcomes present | `loopany artifact get <task-slug>` → body contains `## Outcome` |
| Learning active | `loopany artifact list --kind learning --status active` |
| Proposal terminal | `loopany artifact get require-before-on-metric-tasks` → `status: accepted`, `## Outcome` |
| Skill diff landed | `git log -1 -- skills/loopany-core/kinds/task.md` |
| No duplicate reflect | `evidence` on new learnings excludes already-processed task slugs |

<Warning>
Do not edit `SKILL.md` or kind files without a `skill-proposal` artifact. Direct skill edits bypass the evidence chain and break the "agent proposes, human accepts" constraint from project architecture.
</Warning>

## Status machines (quick reference)

| Kind | Initial | Terminal transitions |
|------|---------|------------------------|
| `task` | `todo` | `done`, `failed` require `## Outcome` (playbook) |
| `learning` | `active` | `superseded`, `archived` require `## Outcome` (playbook) |
| `skill-proposal` | `pending` | `accepted`, `rejected` require `## Outcome` (playbook) |

CLI `artifact status` enforces **legal edges only** (e.g. `pending` → `accepted`). Body section gates are agent discipline unless a future validator adds them.

## Anti-patterns this example avoids

- Reflecting on one task (insufficient pattern).
- Creating a `skill-proposal` without a backing `learning`.
- Re-proposing a `rejected` change without reading `loopany artifact list --kind skill-proposal --status rejected`.
- Accepting without `## Outcome` on the proposal.
- Editing beyond `## Proposed change` scope.

## Automated regression

`test/skill-regression.sh` installs four skills into a temp workspace, seeds a mission, and runs Claude-driven scenarios—including scenario 8 (reflect declines when &lt; 3 tasks). Run from repo root:

```bash
./test/skill-regression.sh        # all scenarios
./test/skill-regression.sh 8      # reflect threshold only
./test/skill-regression.sh --dry-run
```

Requires `claude` CLI and API credentials; see script header for prerequisites.

Unit/E2E CLI tests cover `artifact append` / `status` and lifecycle flows in `test/scenario.e2e.test.ts` (signal → task → `## Outcome` → `done`) but do not automate the full reflect → accept → git path—that remains skill-guided.

## Related pages

<CardGroup>
<Card title="Self-improvement loop" href="/self-improvement-loop">
Concept: Outcome evidence, learnings, proposals, checkAt, and the no-direct-skill-edit rule.
</Card>
<Card title="Reflect workflow" href="/reflect-workflow">
Operational reflect and proposal-apply steps in depth.
</Card>
<Card title="Kinds and validation" href="/kinds-and-validation">
Dynamic schemas, status machines, and immutable write semantics.
</Card>
<Card title="Artifact commands" href="/artifact-commands">
`create`, `append`, `status`, and per-kind `--field` flags used above.
</Card>
<Card title="Build and test" href="/build-and-test">
`bun test`, E2E workspaces, and `skill-regression.sh` requirements.
</Card>
</CardGroup>

---

## 21. Build and test

> bun install, typecheck, bun test unit/e2e, compiled binary via bun build --compile, and skill-regression.sh requirements (claude CLI, API key).

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/21-build-and-test.md
- Generated: 2026-06-05T19:05:11.662Z

### Source Files

- `package.json`
- `tsconfig.json`
- `README.md`
- `test/cli.e2e.test.ts`
- `test/skill-regression.sh`
- `test/helpers/cli.ts`
- `CLAUDE.md`

---
title: "Build and test"
description: "bun install, typecheck, bun test unit/e2e, compiled binary via bun build --compile, and skill-regression.sh requirements (claude CLI, API key)."
---

loopany ships as a Bun-first TypeScript ESM project: `package.json` defines `install`, `typecheck`, `test`, `build`, and `dev` scripts; the default verification path is `bun install`, `bun run typecheck`, and `bun test` (235 tests across 15 files). A compiled CLI lands at `bin/loopany` via `bun build --compile`. Agent-skill behavior is validated separately by `test/skill-regression.sh`, which drives Claude Code with live API calls.

## Prerequisites

| Requirement | Used for |
|-------------|----------|
| [Bun](https://bun.sh) | Runtime, test runner, bundler, and `bun build --compile` |
| TypeScript 5.x (devDependency) | `bun run typecheck` (`tsc --noEmit`) |
| Temp filesystem access | E2E tests create scratch workspaces under the OS temp dir |
| `claude` CLI (≥ 2.0) + Anthropic API credentials | `test/skill-regression.sh` only |

`tsconfig.json` targets ES2022, `moduleResolution: "bundler"`, `strict: true`, and includes `src/**/*` and `test/**/*`. There is no `engines` field in `package.json`; development and tests assume Bun, not Node, for spawning the CLI and running the suite.

## Install dependencies

```bash
bun install
```

This pulls runtime deps (`yaml`, `zod`, `@huggingface/transformers`) and dev deps (`typescript`, `@types/bun`). For a global `loopany` command during local development, also run `bun link` after install (see [Installation](/installation)).

## npm scripts

| Script | Command | Purpose |
|--------|---------|---------|
| `dev` | `bun run src/cli.ts` | Run the CLI from source without compiling |
| `build` | `bun build --compile --outfile bin/loopany src/cli.ts` | Produce a single static binary |
| `test` | `bun test` | Full unit + E2E suite |
| `typecheck` | `tsc --noEmit` | Static type check (no emit) |

<Note>
`CLAUDE.md` mentions `bun run test:e2e`, but that script is **not** defined in `package.json`. Run E2E files explicitly (see below) or use `bun test` for the full suite.
</Note>

## Typecheck

```bash
bun run typecheck
```

Runs `tsc --noEmit` against `src/` and `test/` with Bun types. A clean run produces no output and exit code `0`.

## Test suite overview

`bun test` discovers all `*.test.ts` files under `test/`. The repo uses filename convention to separate layers:

```text
test/
├── *.test.ts              # unit / module tests (11 files)
├── *.e2e.test.ts          # CLI subprocess + real workspace (4 files)
├── helpers/cli.ts         # spawns `bun src/cli.ts` with LOOPANY_HOME
└── skill-regression.sh    # optional; Claude Code + API (not bun test)
```

```mermaid
flowchart TB
  subgraph bun_test["bun test"]
    U[Unit tests<br/>artifact-store, kind-registry, search-store, …]
    E[E2E tests<br/>cli.e2e, search.e2e, scenario.e2e, migration e2e]
  end
  subgraph harness["E2E harness"]
    H[test/helpers/cli.ts]
    CLI[src/cli.ts via Bun.spawn]
    WS["$LOOPANY_HOME temp dir"]
  end
  subgraph skill_reg["skill-regression.sh"]
    CC[claude -p]
    SK[skills: core, capture, reflect, review]
  end
  U --> bun_test
  E --> H --> CLI --> WS
  skill_reg --> CC --> CLI
  skill_reg --> SK
```

### Full run

```bash
bun test
```

Typical result: **235 pass**, **0 fail**, across **15 files** (~45–50s on a developer machine). The suite uses real directories and files; E2E tests do not mock the filesystem.

### Unit tests only

Unit files omit the `.e2e.` segment in the filename. Examples: `test/slug.test.ts`, `test/kind-registry.test.ts`, `test/search-store.test.ts`, `test/artifact-store.test.ts`, `test/migration-framework.test.ts`.

Run a subset:

```bash
bun test test/slug.test.ts test/kind-registry.test.ts
```

Roughly **136 tests** live in the 11 non-`.e2e.` files (total minus the four E2E files).

### E2E tests only

Four files, **99 tests** total:

| File | Focus |
|------|--------|
| `test/cli.e2e.test.ts` | `init`, `artifact/*`, `refs`, `trace`, `domain`, `followups`, `search`, `doctor`, JSON stdout |
| `test/search.e2e.test.ts` | `reindex` / `search` with `--no-embed` (no ONNX download in CI-style runs) |
| `test/scenario.e2e.test.ts` | Full lifecycle: signal → person → task → refs → followups → done + `## Outcome` |
| `test/migration-v0.1-to-v0.2.e2e.test.ts` | v0.1 synthetic workspace → five migration scripts → doctor-clean v0.2 |

Run E2E only:

```bash
bun test \
  test/cli.e2e.test.ts \
  test/search.e2e.test.ts \
  test/scenario.e2e.test.ts \
  test/migration-v0.1-to-v0.2.e2e.test.ts
```

### E2E harness behavior

`test/helpers/cli.ts` resolves `src/cli.ts`, spawns `bun` with `LOOPANY_HOME` set to a fresh temp directory from `newWorkspace()`, and returns `{ stdout, stderr, code }`. Every CLI E2E test exercises the same entrypoint agents use in development (`bun run src/cli.ts`), not the compiled binary.

<Info>
Search E2E intentionally passes `--no-embed` so runs avoid downloading the Hugging Face ONNX embedding model. Semantic embedding behavior is covered in `test/search-store.test.ts` with `NoopEmbedder` / vector math; production `reindex` without `--no-embed` uses `TransformersEmbedder` in `src/core/embedder.ts`.
</Info>

## Compiled binary

```bash
bun run build
```

Equivalent to:

```bash
bun build --compile --outfile bin/loopany src/cli.ts
```

Output: `bin/loopany` (single executable, on the order of tens of MB). `bin/` is listed in `.gitignore` — build locally; do not expect it in git.

Verify:

```bash
./bin/loopany --help
```

Distribution path for agents is still commonly `bun link` + `loopany` from `src/cli.ts` (`package.json` `bin` points at `./src/cli.ts`). The compiled binary is the planned single-artifact ship target described in `CLAUDE.md`.

## Skill regression (`test/skill-regression.sh`)

Separate from `bun test`. Exercises **four** bundled skills (`loopany-core`, `loopany-capture`, `loopany-reflect`, `loopany-review`) through **10** Claude Code scenarios with real `claude -p` calls.

### Prerequisites

- `claude` in `PATH` (Anthropic Claude Code CLI, documented as ≥ 2.0 in the script header)
- Active Anthropic API configuration for `claude`
- `bun` and repo checkout (script uses `bun $REPO_ROOT/src/cli.ts`, not requiring `loopany` on `PATH`)

`loopany-resolver` is not symlinked into the regression harness; scenarios target the four workflow skills above.

### Usage

```bash
./test/skill-regression.sh              # all 10 scenarios
./test/skill-regression.sh 4            # scenario 4 only
./test/skill-regression.sh --dry-run    # print plan; skip claude calls
```

### What the script does

<Steps>
<Step title="Prepare temp environment">
Creates `/tmp/loopany-skill-regression.*` with a fake `HOME` (skills under `~/.claude/skills/`), sets `LOOPANY_HOME` to a fresh workspace, symlinks the four skills.
</Step>
<Step title="Seed workspace">
Runs `loopany init`, creates a test `mission` artifact so bootstrap checks pass.
</Step>
<Step title="Run scenarios">
Each scenario calls `claude -p` with `--bare`, `--permission-mode bypassPermissions`, `--add-dir` for workspace and repo, `--max-turns 15`, and tool allowlist `Bash Read Write Edit Glob Grep`.
</Step>
<Step title="Assert workspace state">
Checks artifact counts, file patterns, quality-gate behavior (e.g. scenario 4 must not create artifacts), reflect threshold (scenario 8), signal→task upgrade (scenario 9).
</Step>
</Steps>

Expect **~5 minutes** for a full pass (10 API-backed agent turns). README notes isolated runs: each scenario shares one seeded workspace, not 10 separate inits.

On failure, the script **disables cleanup** (`trap - EXIT`) and prints log paths (`$TMPBASE/scenario*.log`) so you can inspect Claude output. Exit code `1` when any assertion fails.

### Scenario map

| # | Skill / theme | Validates |
|---|----------------|-----------|
| 1 | loopany-core | Signal created via natural language + CLI |
| 2 | loopany-core | Task created and moved to `running` |
| 3 | loopany-core | Note (fallback kind) |
| 4 | loopany-capture | Quality gate skips trivial typo fix |
| 5 | loopany-capture | Shipped PR → task artifact |
| 6 | loopany-review | Daily followups on empty workspace |
| 7 | loopany-review | Weekly doctor |
| 8 | loopany-reflect | Declines learning when &lt;3 task outcomes |
| 9 | Cross-skill | Signal upgraded to task, signal `addressed` |
| 10 | Kind routing | Competitor intel routed to `signal` |

<Warning>
Skill regression incurs **API cost** and requires network access. It is not part of `bun test`. Use `--dry-run` in CI planning or local scaffolding without calling Claude.
</Warning>

## Recommended verification order

<Steps>
<Step title="Fast gate">
`bun install && bun run typecheck && bun test`
</Step>
<Step title="Binary (optional)">
`bun run build && ./bin/loopany --help`
</Step>
<Step title="Before skill changes">
`./test/skill-regression.sh` (or targeted scenario number)
</Step>
</Steps>

## Troubleshooting

| Symptom | Likely cause | Action |
|---------|----------------|--------|
| `tsc` errors after pull | Types out of sync | `bun install`, re-run `bun run typecheck` |
| E2E timeout / slow run | 88 CLI tests spawn many processes | Run single file: `bun test test/cli.e2e.test.ts` |
| Search E2E vs prod divergence | E2E uses `--no-embed` | Manually test `loopany reindex` without `--no-embed` in a scratch workspace |
| `skill-regression.sh`: command not found `claude` | Claude Code CLI missing | Install Claude Code; ensure `claude` on `PATH` |
| Skill regression auth errors | No API key / expired creds | Configure Anthropic credentials for `claude` |
| Scenario 9 fails | Scenario 1 did not create a signal | Run full suite or fix scenario 1 logs |
| `bin/loopany` missing | Not built or cleaned | `bun run build` |

For workspace health after migrations or failed runs, use `loopany doctor` ([Doctor and troubleshooting](/doctor-and-troubleshooting)).

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Clone, `bun link`, `LOOPANY_HOME`, and agent harness wiring.
</Card>
<Card title="CLI reference" href="/cli-reference">
Full command surface exercised by `cli.e2e.test.ts`.
</Card>
<Card title="Artifact lifecycle example" href="/artifact-lifecycle-example">
Mirrors `test/scenario.e2e.test.ts` and skill-regression cross-skill flows.
</Card>
<Card title="Skills library" href="/skills-library">
Skills installed and tested by `skill-regression.sh`.
</Card>
<Card title="Schema migration" href="/schema-migration">
v0.1→v0.2 path covered by `migration-v0.1-to-v0.2.e2e.test.ts`.
</Card>
</CardGroup>

---

## 22. Doctor and troubleshooting

> doctor checks (workspace, schema version, kinds, artifacts, references, onboarding), common failures (WorkspaceNotFound, Zod validation, missing search index), and factory/reindex recovery steps.

- Page Markdown: https://grok-wiki.com/public/docs/superdesigndev-loopany-97bd9ab97ae8/pages/22-doctor-and-troubleshooting.md
- Generated: 2026-06-05T19:03:52.956Z

### Source Files

- `src/commands/doctor.ts`
- `src/core/engine.ts`
- `src/commands/search.ts`
- `src/commands/reindex.ts`
- `test/cli.e2e.test.ts`
- `INSTALL_FOR_AGENTS.md`
- `src/commands/migrate.ts`

---
title: "Doctor and troubleshooting"
description: "doctor checks (workspace, schema version, kinds, artifacts, references, onboarding), common failures (WorkspaceNotFound, Zod validation, missing search index), and factory/reindex recovery steps."
---

`loopany doctor` runs deterministic integrity checks over `$LOOPANY_HOME` and exits **0** when every check with status `fail` is clear; **warnings** (`warn`) do not fail the command. The command bootstraps with `skipVersionCheck: true`, so it can report schema mismatches instead of throwing `SchemaVersionMismatchError`. Search health is separate: `loopany search` warns when `search.db` is missing; rebuild with `loopany reindex`.

## Command surface

```bash
loopany doctor [--format human|json]
```

| Output | Behavior |
|--------|----------|
| Human (default) | Columnar check lines with `✓` / `⚠` / `✗` and indented `problems` |
| JSON | `{ workspace, checks[], ok }` — each check has `name`, `status`, `detail`, optional `problems[]` |
| Exit code | `0` if `ok: true`; `1` if any check has `status: "fail"` |

<ParamField body="--format" type="human | json">
Human is the default. JSON is intended for agents and weekly review scripts (`loopany doctor --format json`).
</ParamField>

<ResponseField name="ok" type="boolean">
`true` only when no check has `status: "fail"`. Warnings still yield `ok: true`.
</ResponseField>

## What doctor checks

Doctor is **integrity-only**: no LLM passes, no body-content heuristics (TODO scans belong in reflect), and **no `search.db` probe**.

```mermaid
flowchart TB
  subgraph bootstrap["Bootstrap (doctor only)"]
    B["bootstrap({ skipVersionCheck: true })"]
  end
  subgraph checks["Deterministic checks"]
    W[workspace]
    SV[schema version]
    K[kinds]
    A[artifacts + Zod]
    R[references / dangling edges]
    O[onboarding: self + active mission]
    MC[mission coverage — warn]
    DC[domain coverage — warn]
  end
  B --> W --> SV --> K --> A --> R --> O --> MC --> DC
```

| Check | Status on problem | What it validates |
|-------|-------------------|-------------------|
| `workspace` | always `ok` if doctor runs | Bootstrap succeeded; reports `engine.root` |
| `schema version` | `fail` | `config.yaml` `schemaVersion` vs binary `SCHEMA_VERSION` (`0.2.0`) |
| `kinds` | `fail` | Every kind under `kinds/` (and enabled domain packs) parses; lists `registry.issues` |
| `artifacts` | `fail` | Every indexed artifact’s frontmatter passes its kind’s dynamic Zod schema |
| `references` | `fail` | No dangling edges in `references.jsonl` or implicit graph (missing `from`/`to`) |
| `onboarding` | `fail` | Artifact id `self` exists; ≥1 `mission` with `status: active` |
| `mission coverage` | `warn` | Each `task` has a `mentions[]` entry pointing at a mission id (skipped if no missions) |
| `domain coverage` | `warn` | Artifacts with `domain` set use a value in `enabled_domains` |

<Warning>
A fresh `loopany init` workspace fails **onboarding** until you create `--slug self` person and an active mission — matching `ONBOARDING.md` Phase 3 and e2e expectations.
</Warning>

### Onboarding expectations

Doctor requires:

- `loopany artifact create --kind person --slug self ...`
- At least one `loopany artifact create --kind mission --status active ...`

Legacy v0.1 ids like `prs-self` are migrated to `self` by `skills/migrations/v0.1.0-to-v0.2.0/`; doctor keys off the v0.2 slug id `self`.

## What doctor does not check

| Gap | Where to handle it |
|-----|-------------------|
| Missing or stale `search.db` | `loopany reindex`; `loopany search` stderr hint |
| Resolver / skill injection registered | Roadmap only (`INSTALL_FOR_AGENTS.md`); not in doctor today |
| Semantic body quality | `loopany-reflect` skill |
| Auto-reindex after each write | By design — reindex is explicit in v1 |

## Bootstrap gates vs doctor

Most commands call `bootstrap()` **with** the schema guard. Doctor and migrate are exceptions.

```text
$LOOPANY_HOME/
  kinds/          ← must exist or WorkspaceNotFoundError
  config.yaml     ← schemaVersion matched unless skipped
  artifacts/
  references.jsonl
  search.db       ← optional; not part of doctor
```

| Error | When | Message / recovery |
|-------|------|-------------------|
| `WorkspaceNotFoundError` | No `kinds/` under workspace root | `No loopany workspace at {root}. Run loopany init first.` |
| `SchemaVersionMismatchError` | `config.schemaVersion() !== SCHEMA_VERSION` on normal bootstrap | Points at `skills/migrations/v{from}-to-v{to}/SKILL.md` and `loopany migrate v{from}-to-v{to}` |
| Doctor still runs on mismatch | `skipVersionCheck: true` | `schema version` check `fail` with `run loopany migrate v…-to-v…` in `problems` |

<Info>
`LOOPANY_SKIP_VERSION_CHECK=1` also bypasses the guard (for migration scripts). `loopany migrate` always skips the check so it can describe the next migration on stale workspaces.
</Info>

Absent `schemaVersion` in `config.yaml`, the runtime assumes **`0.1.0`** (`ASSUMED_LEGACY_VERSION`) until `init` or migration writes the current version.

## Common failures and fixes

### WorkspaceNotFound

```bash
loopany init
# or
LOOPANY_HOME=/path/to/brain loopany init
```

Default root: `~/loopany` unless `LOOPANY_HOME` is set.

### Schema version mismatch

Symptom on most commands:

```text
Error: Workspace schema is v0.1.0 but this binary expects v0.2.0. Read skills/migrations/v0.1.0-to-v0.2.0/SKILL.md and run loopany migrate v0.1.0-to-v0.2.0.
```

<Steps>
<Step title="Inspect without crashing">
Run `loopany doctor` or `loopany migrate` — both bypass the version guard and report the gap.
</Step>
<Step title="Discover migration">
```bash
loopany migrate
loopany migrate v0.1.0-to-v0.2.0
```
`migrate` prints `SKILL.md` and ordered `bun run …/scripts/*.ts [--apply]` paths; the CLI does not auto-run migrations.
</Step>
<Step title="Run scripts, verify">
Execute migration scripts per the skill (typically dry-run first, then `--apply`). Finish with `loopany doctor` — `schema version` should be `ok`.
</Step>
</Steps>

Pre-migration doctor failures should be fixed **before** migrating (`skills/migrations/v0.1.0-to-v0.2.0/SKILL.md`).

### Zod validation (CLI writes)

`artifact create`, `set`, and `status` validate input at write time. Failures surface as:

```text
Invalid input:
  - status: Invalid enum value...
```

Doctor re-validates **all** on-disk artifacts: problems look like `{id}: {first zod message}`. Fix by correcting frontmatter (`artifact set`) or editing the markdown file to match `kinds/<kind>.md`, then re-run doctor.

### Broken kind definitions

Kind load errors are **tolerated at boot** but fail the **kinds** check:

```text
kinds/some-kind.md: <parse or schema error>
```

Repair the kind markdown under `~/loopany/kinds/` (or domain `domains/<name>/kinds/`), then doctor again.

### Dangling references

Sources include:

- `loopany refs add` to a missing id
- Hand-edited `references.jsonl`
- Body `[[wiki-links]]` to non-existent slugs (indexed as implicit edges)

Doctor lists lines like:

```text
dangling: task-foo → bar-baz (led-to)
```

Create the target artifact, remove the edge, or fix the link text.

### Onboarding incomplete

```text
self person artifact missing — run onboarding (Phase 3 step 2)
no active mission — run onboarding (Phase 3 step 3)
```

Complete onboarding artifacts or follow `ONBOARDING.md` with your agent host.

### Mission / domain warnings

These set `status: "warn"`; exit code stays **0**.

- **Mission coverage:** add `--mentions <mission-id>` on tasks, or accept orphan tasks.
- **Domain coverage:** `loopany domain enable <name>` or clear/remove invalid `domain` on artifacts.

## Search index: missing or stale

`loopany search` does **not** error when `search.db` is absent:

```text
loopany: no search index found — run `loopany reindex` first.
```

Stdout is `[]`; exit code **0** (scripts can probe safely).

### Reindex

```bash
loopany reindex [--force] [--no-embed]
```

| Flag | Effect |
|------|--------|
| (none) | Incremental: skip artifacts whose mtime matches `search.db` |
| `--force` | Delete `search.db` and rebuild FTS5 + embeddings from scratch |
| `--no-embed` | Use `NoopEmbedder` (fast CI; keyword/FTS still work) |

<ResponseField name="stdout JSON" type="object">
`{ indexed, skipped, removed, embedder: "transformers" | "noop" }`. Creates `$LOOPANY_HOME/search.db` on first run. Artifact CRUD does not auto-update the index in v1.
</ResponseField>

<Steps>
<Step title="Populate after init or bulk edit">
```bash
loopany reindex --no-embed   # quick smoke / CI
loopany reindex              # full hybrid (downloads embed model on first run)
```
</Step>
<Step title="Verify search">
```bash
loopany search "your query" --kind note --limit 10
```
</Step>
<Step title="Recover deleted artifacts in index">
Re-run `reindex` without `--force`; removed on-disk files are dropped from the DB (`removed` count in JSON).
</Step>
</Steps>

## Factory UI (exploration, not repair)

```bash
loopany factory [--port N] [--host H] [--no-open]
```

- Default: `http://127.0.0.1:4242`, opens browser unless `--no-open`
- Requires **normal** bootstrap (schema version must match)
- Read-only walkable view of nodes/edges from the same index doctor uses for references (not search)

Use factory to **see** graph shape after doctor is green; use **reindex** when search is empty. Factory does not fix validation or migration issues.

## Operational playbooks

### After `loopany init` or agent install

```bash
loopany doctor --format json
# fix onboarding → kinds → artifacts → references
loopany reindex --no-embed
loopany search "smoke" --limit 3
```

### Weekly review (agent)

Per `skills/loopany-review/references/weekly.md`: run `loopany doctor --format json`, summarize failures, propose fixes — **do not auto-fix** without user confirmation.

### Stale workspace on new binary

1. `loopany doctor` → read `schema version` problems  
2. `loopany migrate` → run scripted migration with git snapshot per skill README  
3. `loopany doctor` → all `fail` checks clear  
4. `loopany reindex [--force]` if you rely on search  

### Machine-readable health

```bash
loopany doctor --format json | jq '.ok, .checks[] | select(.status=="fail")'
```

Exit **1** when `ok` is false — suitable for cron or CI **after** onboarding is complete.

## JSON report shape (reference)

```json
{
  "workspace": "/Users/you/loopany",
  "ok": false,
  "checks": [
    {
      "name": "schema version",
      "status": "fail",
      "detail": "workspace v0.1.0, binary expects v0.2.0",
      "problems": ["run `loopany migrate v0.1.0-to-v0.2.0`"]
    }
  ]
}
```

Human output capitalizes check names and pads columns; semantics match JSON.

<Tip>
`VERSION` (package semver) and `SCHEMA_VERSION` (workspace format) drift independently — bumping the CLI does not always require migration; bumping `SCHEMA_VERSION` always ships a `skills/migrations/v*-to-v*/` directory.
</Tip>

## Related pages

<CardGroup>
<Card title="Workspace setup" href="/workspace-setup">
Init, bundled kinds, onboarding, and first doctor pass.
</Card>
<Card title="Schema migration" href="/schema-migration">
`SchemaVersionMismatchError`, migrate discovery, and v0.1.0→v0.2.0 scripts.
</Card>
<Card title="Graph, search, and scheduling" href="/graph-search-commands">
`reindex`, `search`, `factory`, refs/trace, and followups.
</Card>
<Card title="Kinds and validation" href="/kinds-and-validation">
Kind markdown, dynamic Zod schemas, and immutable write rules.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`LOOPANY_HOME`, `LOOPANY_SKIP_VERSION_CHECK`, and `config.yaml` keys.
</Card>
<Card title="CLI reference" href="/cli-reference">
Full command surface and JSON stdout conventions.
</Card>
</CardGroup>

---
