Agent-readable docs
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.
Pages
- OverviewWhat loopany exposes (CLI, workspace, skills), runtime assumptions (Bun, $LOOPANY_HOME), and the shortest path from init to a running mission-backed brain.
- InstallationClone 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.
- QuickstartFirst successful session: init workspace, create a mission and task, add a reference edge, run doctor, and optional reindex/search or factory UI.
- Artifacts and workspaceOn-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.
- Kinds and validationMarkdown kind definitions, dynamic Zod frontmatter schemas, status machines, indexedFields, slug rules, and immutable write semantics (append, status flip, supersede).
- Reference graphAppend-only references.jsonl edges, implicit mentions from frontmatter, canonical relation verbs, refs/trace queries, and wiki-link [[id]] conventions.
- DomainsScope-local packs under domains/<name>/, enabled_domains in config.yaml, domain-scoped kind overrides, and when to propose a domain vs a note.
- Skills libraryAgent-readable SKILL.md packs (resolver, core, capture, reflect, review), routing table, cross-skill chaining, and harness-neutral memory injection.
- Self-improvement loopTask ## Outcome evidence, learning beliefs, skill-proposal accept/reject flow, checkAt followups, and the rule that agents never edit skills directly.
- Workspace setupRun loopany init, verify bundled kinds copied, complete ONBOARDING.md once, register cadence (cron vs session-boundary), and confirm doctor passes.
- Capture workflowEnd-of-task capture quality gate, event→kind routing (task/signal/note), subagent dispatch pattern, and duplicate detection via artifact list --contains.
- Reflect workflowGather recent outcomes and dismissed signals, pattern thresholds, write learning and skill-proposal artifacts, and accept/reject proposals with git-backed skill diffs.
Complete Markdown
# 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/<dirName>/<slug>.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}\-_]+
loopany Documentation · Grok Docs
(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 <proposal-slug>” / “reject <proposal-slug>” 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 < 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 <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>
---