Every command, every flag, in one place.
eidolons <command> [options]
| Variable | Default | Purpose |
|---|---|---|
EIDOLONS_HOME |
~/.eidolons |
Where nexus + cache live |
EIDOLONS_REPO |
https://github.com/Rynaro/eidolons |
Nexus repo (for bootstrap) |
EIDOLONS_REF |
main |
Nexus ref for CLI self-pin (for bootstrap). Written to .install_ref; eidolons upgrade self updates this. |
EIDOLONS_ROSTER_REF |
main |
Nexus ref for roster-refresh target (for bootstrap). Written to .roster_ref; nexus_refresh reads this. Change to pin a project to a specific roster branch or tag. |
EIDOLONS_BIN_DIR |
~/.local/bin |
Where the CLI is symlinked |
EIDOLONS_INTEGRITY_ENFORCEMENT |
roster setting | Override release integrity mode (warn or strict) |
EIDOLONS_SKIP_REFRESH |
0 |
Set to 1 to disable auto-refresh of the nexus cache (offline-first / deterministic builds). |
| Path | Scope | Purpose |
|---|---|---|
~/.eidolons/nexus/ |
user | Cloned nexus |
~/.eidolons/nexus/.install_ref |
user | CLI self-pin ref (written by install.sh and upgrade self). |
~/.eidolons/nexus/.roster_ref |
user | Roster-refresh target ref (written by install.sh only; upgrade self leaves this alone). Default: main. Installs predating v1.11.0 that lack this file are auto-healed by nexus_refresh() and upgrade self: the file is backfilled with $EIDOLONS_ROSTER_REF when set, otherwise main. As part of the backfill (v1.13.4), nexus_ensure_roster_ref also ensures .roster_ref and all other sidecar files (.install_date, .install_ref, .install_commit) are listed in ~/.eidolons/nexus/.gitignore so they are not reported as untracked by git status. Without this heal, a freshly-backfilled .roster_ref caused eidolons upgrade self's dirty-tree check to refuse the upgrade until --force was passed. |
~/.eidolons/cache/ |
user | Cloned Eidolon repos (per name + version) |
./eidolons.yaml |
project | Your team manifest |
./eidolons.lock |
project | Resolved versions |
./.eidolons/<n>/ |
project | Installed Eidolon files |
Initialize a project. Detects greenfield vs brownfield.
eidolons init [--preset NAME | --members LIST] [--hosts LIST] [--force] [--non-interactive]
| Flag | Purpose |
|---|---|
--preset NAME |
Use a preset (minimal, pipeline, full, ...). See eidolons list --presets. |
--members LIST |
Comma-separated Eidolon names. Mutually exclusive with --preset. |
--hosts LIST |
claude-code,copilot,cursor,opencode,codex,all. Default: auto. |
--force |
Overwrite existing eidolons.yaml. |
--non-interactive |
Fail on any prompt. Requires --preset or --members. |
Output: eidolons.yaml, eidolons.lock, plus per-Eidolon installs.
init delegates to sync after writing eidolons.yaml. The cortex is wired automatically via sync: EIDOLONS.md is mirrored to .eidolons/cortex/, deep tables are mirrored alongside it, and a marker-bounded pointer block is injected into the root host-docs when shared-dispatch is on (see sync below).
Add one or more members to an existing project.
eidolons add <name>... [--version SPEC] [--non-interactive]
Calls sync after writing to eidolons.yaml.
Remove a member cleanly: manifest entry, .eidolons/<n>/, bounded sections in dispatch files, and lock entry.
The cortex cleanup runs now (ahead of the full v1.1 implementation): eidolons remove strips the <!-- eidolon:cortex start/end --> block from all root host-docs and deletes .eidolons/cortex/ when the named Eidolon is the last installed member. If other Eidolons remain, the cortex blocks are preserved.
Reconcile installed state to eidolons.yaml. Idempotent.
eidolons sync [--non-interactive] [--dry-run] [--no-heal]
- Fetches each member's repo (cached in
~/.eidolons/cache/). - Runs each per-Eidolon
install.shwith the right--hostsand--target. - Aggregates per-Eidolon manifests into
eidolons.lock. - Mirrors
EIDOLONS.mdto.eidolons/cortex/EIDOLONS.mdand mirrors the deep companion tables (trance-matrix.md,handoff-graph.md,validation-gates.md,README.md) frommethodology/cortex/to.eidolons/cortex/. - When
shared_dispatch: true, injects a marker-bounded<!-- eidolon:cortex start/end -->pointer block into rootAGENTS.md,CLAUDE.md, and.github/copilot-instructions.mdso the host LLM is directed to the cortex at session start. The block is omitted when shared-dispatch is off. - When the harness is installed, refreshes the hook shims and self-heals a stale
startup-only Claude CodeSessionStartmatcher in.claude/settings.jsonto the canonicalstartup|resume|clear|compact(only the Eidolons-owned entry is touched; idempotent). Pass--no-healto skip the heal.
| Flag | Effect |
|---|---|
--no-heal |
Skip the seamless SessionStart-matcher self-heal during the harness-shim refresh (default: heal a stale startup-only matcher in place). |
Show Eidolons.
eidolons list # auto: installed if in a project, available otherwise
eidolons list --available # everything in the nexus roster
eidolons list --installed # only what's in this project
eidolons list --presets # named presets
eidolons list --json # JSON
Detailed roster inspection.
eidolons roster # full team summary
eidolons roster atlas # single member detail
eidolons roster atlas --methodology # just methodology
eidolons roster atlas --handoffs # handoff contracts
eidolons roster atlas --references # research references
eidolons roster atlas --json # raw JSON
Health check: manifest/lock consistency, per-member installs, host dispatch
wiring, release-integrity status surfaced from eidolons.lock, and (when
.mcp.json contains an atlas-aci entry) ghcr.io registry reachability.
eidolons doctor # fast checks only (14 structural sections)
eidolons doctor --fix # fast checks + attempt auto-repair via sync
eidolons doctor --deep # fast checks + D1..D6 methodology integrity gates
eidolons doctor --deep --fix # same as --deep; --fix is read-only for D1..D6
| Flag | Purpose |
|---|---|
--fix |
Attempt to auto-repair simple structural issues (lockfile drift, missing host wiring). Read-only for methodology gates (D1..D6). |
--deep |
Run methodology-integrity gates D1..D6 after the fast checks. Required to catch broken outbound links, token-budget overruns, and content drift vs the release manifest. |
Deep checks (--deep):
| ID | Name | Threshold |
|---|---|---|
| D1 | agent.md token budget |
MUST ≤ 1000 tokens (wc -w × 4/3) |
| D2 | agent.md outbound link resolution |
All (skills|templates|schemas)/*.{md,json,y[a]ml} refs MUST resolve |
| D3 | SPEC.md outbound link resolution |
Same as D2 against SPEC.md (absent on legacy installs → warn) |
| D4 | manifest_sha256 vs lock | MUST match; WARN-skip when lock has no sha (legacy / pre-1.4) |
| D5 | Host-vendor agent body contract | MUST reference agent.md + SPEC.md; zero legacy <UPPER>.md refs |
| D6 | Skills dual-write SHA parity | .eidolons/<n>/skills/*.md ↔ .claude/skills/<n>-<basename>/SKILL.md SHA MUST match |
D1..D6 are read-only: they report drift but never mutate .eidolons/. To repair methodology issues, run eidolons sync (re-installs each member) or eidolons remove <member> && eidolons add <member>.
The "Release integrity" section is read-only and derives entirely from the
verification field of each eidolons.lock member. To re-check against the
roster (commit/tree/archive checksum, manifest re-hash) run eidolons verify.
ghcr.io reachability probe (Check 7): when .mcp.json contains an
atlas-aci entry with a ghcr.io/rynaro/atlas-aci@sha256:<digest> arg, doctor
performs an anonymous-token HEAD against the ghcr.io v2 manifests endpoint:
pass: atlas-aci image reachable on ghcr.io— digest is publicly reachable.warn: atlas-aci image not reachable (offline? or pinned digest yanked? — try 'eidolons mcp atlas-aci pull --build-locally')— network error or 404.
This probe is non-fatal: it does not increment the error count or change
the doctor exit code. It is silently skipped when .mcp.json is absent, when
there is no atlas-aci entry, or when curl is not on PATH.
Pending Upgrades section: doctor reads the roster's versions.pins.stable
for each declared member and reports any whose installed version (per
eidolons.lock) is behind. Two states:
<member> <installed> → <latest> (within <constraint>)— applyingeidolons upgrade <member>would advance the lock.<member> <installed> → <latest> (constraint <constraint> — bump to allow)— pinned-out: the constraint pins below the new latest, soupgradeis blocked until you edit the constraint ineidolons.yaml.
The section is informational only — it does not increment the error count or
change the exit code. Network failures degrade silently; the section is
skipped entirely when eidolons.yaml is missing.
Mechanical memory pre-flight (GAP-2). Surfaces prior project context from CRYSTALIUM into the SessionStart harness injection.
eidolons memory preflight [--query <s>] [--ttl <sec>] [--no-cache] [--timeout <sec>] [--explain]
Runs a bounded one-shot CRYSTALIUM recall (out-of-MCP-session path) and emits a compact injectable digest. Stdout IS the digest and nothing else. All failure paths emit empty stdout and exit 0.
| Flag | Purpose |
|---|---|
--query <s> |
Recall query. Default: "project <slug> recent context" (deterministic slug from basename(PWD) → lowercase → alnum-hyphen). Stable → TTL cache key is stable. |
--ttl <sec> |
Cache TTL in seconds. Default: 900 (15 min). Env: EIDOLONS_MEMORY_PREFLIGHT_TTL. |
--no-cache |
Bypass cache read AND write. Always runs live; never persists the result. |
--timeout <sec> |
Docker kill timeout in seconds. Default: 8. Env: EIDOLONS_MEMORY_PREFLIGHT_TIMEOUT. |
--explain |
Human-readable diagnostic mode (NOT for hook injection): gate status, cache hit/miss/age (read-only — never writes the cache), resolved docker invocation, recall exit code, record count, total_tokens, slot_breakdown, scope/layers. On a successful recall returning 0 records, prints an explicit 0 records returned — store may be empty, mis-scoped, or filtered (status/scope)… line. Use when memory "works" but nothing ever comes back. |
Docker args source: reads .mcp.json mcpServers.crystalium.args (the already-rendered invocation). Transforms:
- Strips
-i(interactive stdin flag — one-shot needs no stdin). - Strips
--name <value>pair (avoids collision with the runningcrystalium-<slug>serve container). - Replaces trailing
python -m crystalium servewithpython -m crystalium recall --query Q --scope-project SLUG --k 5 --format json. - Keeps image ref,
-vvolume,-e CRYSTALIUM_*env,--cap-drop ALL,--security-optverbatim.
TTL cache: .eidolons/harness/cache/preflight.json — shape {cached_at, query, digest}. Cache HIT when (now - cached_at) < ttl AND query == default_query. Cache is project-local; stale entries rerun docker automatically.
Timeout: prefers timeout(1) if available (Linux/coreutils); falls back to background-watcher idiom (bash 3.2 safe) when absent (macOS default shell).
Crystalium-present gate: both .mcp.json mcpServers.crystalium AND eidolons.mcp.lock name: crystalium must be present. Either absent → empty stdout, exit 0.
Digest format: one [layer/trust_tier] summary line per record, truncated at 1500 chars.
SessionStart arm: eidolons memory preflight is called automatically from harness_hook.sh session_start mode (R28). Activates on eidolons upgrade self; no harness install --force needed. The digest is appended to additionalContext under a ## Prior project memory (CRYSTALIUM recall) heading.
Environment:
| Variable | Default | Purpose |
|---|---|---|
EIDOLONS_MEMORY_PREFLIGHT_TTL |
900 | Cache TTL override (seconds) |
EIDOLONS_MEMORY_PREFLIGHT_TIMEOUT |
8 | Docker kill timeout override (seconds) |
Mechanical hook wiring — writes host-native hook shims so every prompt submitted inside a supported AI coding host is automatically enriched with routing context.
eidolons harness install [--hosts HOST,...] [--strict] [--force]
eidolons harness remove
eidolons harness status
eidolons harness run --hook <host> [--session-start] [--stdin] [--verify] [--verify-block]
Writes shim scripts under .eidolons/harness/hooks/ and merges hooks blocks into host config files.
| Flag | Purpose |
|---|---|
--hosts HOST,... |
Comma-separated host subset (claude-code, codex, copilot, cursor, opencode). Default: all hosts in eidolons.yaml hosts.wire. |
--strict |
Enable the strict enforcement tier (see below). |
--protect GLOB |
Add a file-glob to the protected set. May be repeated. Protected globs are denied in ALL contexts including subagents. |
--force |
Overwrite existing shims even when already up-to-date. (The SessionStart merge is an upsert: a stale startup-only matcher on the Eidolons-owned entry is healed in place.) |
--no-heal |
Skip the seamless SessionStart-matcher self-heal during the internal --refresh-shims-only refresh (default: heal). |
Base tier (default): writes UserPromptSubmit.sh + SessionStart.sh shims for claude-code and codex; sessionStart shim for copilot. Merges a hooks block into .claude/settings.json (claude-code) and .codex/hooks.json (codex). Cursor and opencode receive no base shims (their surfaces ride eidolons sync and eidolons mcp install).
Strict tier (--strict): adds a PreToolUse shim layer on top of the base tier.
| Host | Strict mode | Shim output | Caveat |
|---|---|---|---|
| claude-code | block |
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"..."}} (exit 0) |
Subagent invocations (agent_id present in event JSON) are allowed through — delegate-or-deny. Protected globs deny in ALL contexts. |
| codex | advisory |
{"decision":"block","reason":"..."} (exit 0) |
Protected-glob denials only; host may still proceed. |
| opencode | advisory |
Plugin .opencode/plugins/eidolons.js throws on edit tools |
Caveat: opencode#5894 subagent bypass — plugin not invoked for subagent calls. |
| cursor | refused | CLI prints reason and exits non-zero | beforeSubmitPrompt persist-in-context bug makes strict surfaces unsound (FORGE degradation-rule 4). |
| copilot | — | Not available | No PreToolUse-equivalent hook in copilot. |
#16952 guard: The UPS shim and eidolons run both exit 0 immediately when the prompt is a task-completion notification ("Agent ... completed" shape or <task-notification> suffix). This prevents double-routing on subagent hand-off completions (Claude Code issue #16952).
Idempotency: repeat runs produce byte-identical output (lock + shim files). Use --force to re-write even when shims appear current.
Lock record (eidolons.lock harness:):
harness:
schema_version: "1"
hosts_wired: [claude-code, ...]
shim_paths: [.eidolons/harness/hooks/claude-code/UserPromptSubmit.sh, ...]
settings_json_patched: true
codex_hooks_json_patched: true
strict: [claude-code] # present only when --strict used
strict_modes: # present only when --strict used
claude-code: block
protect: ["src/**", ...] # present only when --protect usedReverses harness install: removes shim files, reverts the settings/hooks JSON merges, removes the opencode advisory plugin if present, and clears the harness: key from eidolons.lock. Preserves all sibling keys in merged JSON files.
Read-only report of the current harness state. Reads eidolons.lock and performs existence checks on shim files.
- Reports per-host effective tier and enforcement mode (
[inject-only],[strict:block],[strict:advisory]). - Lists strict-refused hosts (e.g., cursor in manifest wire set).
- Reports shim presence (
[present]/[MISSING]). settings.json patched/codex hooks patchedare reality probes, not lock reads: true iff the host file exists AND wires an eidolons hook (exact match on the host-scoped recorded shim path when available; conservativeeidolonsgrep fallback; probe failures degrade tofalse).- Reports cursor static surfaces (
.mdc+ AGENTS.md dispatch-pointer) when cursor is inhosts.wire. - Warns about
.codex/agents/<name>.mdfiles (Codex only reads.toml— G10).
Vendor-neutral routing kernel. Reads a prompt and emits a host-dialect hook artifact.
| Flag | Purpose |
|---|---|
--hook HOST |
Output mode: wrap routing artifact in HOST-dialect hookSpecificOutput JSON. The injected context includes the route, tier, chain, and the per-step model tiers (model tier: <t> / model tiers: atlas=standard → spectra=deep → …) with an instruction to honor them via the host's model selection mechanism. |
Routing-1.1 artifact fields (additive, from roster/routing.yaml): degraded_mode / degraded_mode_per_step (weak-host loop shape per Eidolon), escalation (surfaced verbatim from the selected Eidolon — execution belongs to the orchestrator/sandbox), fallthrough_reason (declared-fallback when the host-tier gate routed to a declared fallback: peer), and — at trance tier only — model_tier_lead / model_tier_workers.
| --session-start | Emit cortex digest (used by SessionStart shims). |
| --stdin | Read prompt from event JSON .prompt field on stdin instead of positional arg. |
| --verify | Verify the ECL envelope on an incoming hand-off before routing (warn mode). |
| --verify-block | Same as --verify but exit 3 on tampered payload. |
eidolons doctor --deep includes gate D12 — harness lock⇄files consistency:
- Reads
harness.shim_pathsfrom the lock and asserts every shim exists and is executable. - Asserts
settings.jsonvalid JSON + UserPromptSubmit entry present (when claude-code wired). - Asserts
codex hooks.jsonvalid JSON (when codex wired). - Asserts
opencode.jsonvalid JSON (when opencode wired). - Asserts strict hosts never include cursor (unsound surface).
- Warns on orphan shims (exist on disk but not in lock) and advisory plugin without strict record.
- Skips entirely when
harness.schema_versionis absent from the lock.
Exit 0 when D12 passes or skips; the harness error counter increments on FATAL items.
D13 — memory recallability (project-level, crystalium-gated):
- Skips cleanly when crystalium is not in both
.mcp.jsonandeidolons.mcp.lock. - Runs the container's
crystalium doctorand arecall --k 3probe via the same docker-args transform asmemory preflight(shared plumbing:cli/src/lib_memory_probe.sh); hard 10 s timeout each. - PASS with record count, or
WARN crystalium store returns 0 records — memory is effectively write-only (check scope keys, crystal status, embeddings). Unreachable/timeout ⇒ WARN. Never FATAL.
D14 — routing shape (nexus-level, fatal — this is data integrity, not a probe): validates roster/routing.yaml 1.1 keys mechanically — degraded_mode ∈ {fanout, sample-select}, fallback/escalation.reroute_to reference existing eidolons (dangling = FAIL), escalation.on_fail ∈ {reroute, escalate-tier}, max_escalations int ≥ 1, trance.{lead,workers}_tier ∈ {light, standard, deep}.
Vendor-neutral model management: assign each Eidolon a tier (light < standard < deep), pick a vendor profile, and let the nexus resolve and write the concrete model: into host agent frontmatter.
eidolons model # interactive picker (TTY) / usage (non-interactive)
eidolons model list # tier ladder + profiles (active marked) + tier→model maps
eidolons model show [<eidolon>] [--json]# resolved table: tier · profile · source · effective model
eidolons model use <eidolon>@<tier> # set a per-member tier override (light|standard|deep)
eidolons model use <eidolon>@<model> # pin a concrete model for one Eidolon (escape hatch)
eidolons model profile <name> # switch active profile; re-resolve + re-wire all members
eidolons model reset [<eidolon>] # clear a member's override/pin (all + calibration if no arg)
Profiles (the sole home for vendor model strings) live in roster/model-profiles.yaml; per-Eidolon suggested tiers live in roster/routing.yaml. The resolved effective model is recorded in eidolons.lock and written to .claude/agents/<id>.md (claude-code) / .codex/agents/<id>.md (codex) inside an idempotent # eidolons:managed model block; copilot/cursor are a no-op. Exit codes: 0 ok · 2 bad args / unknown Eidolon or profile · 3 resolve hard-miss · 4 frontmatter write failed. eidolons doctor --deep (gate D9) reports frontmatter-vs-lock drift. Full reference: docs/model.md.
Read-only release integrity verification for installed members.
eidolons verify
eidolons verify atlas spectra
For releases with roster metadata, mismatched commits, Git trees, archive checksums, or installed manifest checksums fail the command. Members without release metadata warn in compatibility mode.
Layer 2 methodology integrity: compare installed Eidolons against a fresh re-derivation from their upstream-pinned releases.
eidolons verify-release
eidolons verify-release --eidolon atlas --eidolon spectra
eidolons verify-release --strict
eidolons verify-release --no-fetch
eidolons verify-release --json
| Flag | Purpose |
|---|---|
--eidolon NAME |
Verify only this Eidolon. Repeatable. Unknown name → error. |
--strict |
Exit non-zero on any drift. Default: WARN-only (exit 0 even with drift). |
--no-fetch |
Use per-version cache only; do not fetch upstream. Cache miss → error. Useful for offline CI. |
--json |
Emit a machine-readable JSON report on stdout (see schema below). |
What it does. For each Eidolon in eidolons.lock, verify-release:
- Fetches (or reuses the cache for) the pinned upstream version.
- Runs the per-Eidolon
install.shinto a temp directory, honoring the same--hostsand--shared-dispatchflags aseidolons sync. - SHA-256 diffs every file in the temp install against the consumer's on-disk
.eidolons/<name>/tree. - Reports per-file status: OK (matching), DIFFER (different SHA), MISSING (in upstream, not on consumer), EXTRA (on consumer, not in upstream).
- Excludes
install.manifest.jsonfrom the diff — timestamp drift is expected.
Catches:
- Local file tampering after install.
- Mid-install corruption that fooled
doctor --deepD4 (lock + files matched each other but both differ from upstream). - Accidentally deleted files under
.eidolons/<name>/. - Files added under
.eidolons/<name>/that aren't part of the install.
Exit codes:
| Code | Condition |
|---|---|
| 0 | All checks ran; no drift — or drift detected and --strict not passed. |
| 1 | --strict passed and at least one drift detected. |
| 1 | Hard error (no eidolons.lock, unknown --eidolon, cache miss with --no-fetch, installer failure). |
| 2 | Unknown flag. |
--json schema:
{
"cli_version": "1.12.0",
"checked_at": "2026-05-26T14:32:17Z",
"strict": false,
"summary": { "verified": 5, "drifted": 1, "errors": 0 },
"members": [
{
"name": "atlas",
"version": "1.7.1",
"status": "ok",
"file_count": 124,
"diff": []
},
{
"name": "spectra",
"version": "4.5.1",
"status": "drift",
"file_count": 98,
"diff": [
{ "status": "DIFFER", "path": "skills/planning/SKILL.md",
"tmp_sha": "a3f5...", "consumer_sha": "9e21..." },
{ "status": "EXTRA", "path": "skills/custom-user-skill.md",
"tmp_sha": null, "consumer_sha": "c0ff..." }
]
}
]
}status per member: ok | drift | error. diff[] entries: status in DIFFER | MISSING | EXTRA.
Remediation: drift is diagnostic only. verify-release is read-only and never repairs. To restore:
eidolons sync # all members
eidolons remove <name> && eidolons add <name> # one member
Layer 3 integrity: print an Eidolon's canary mission prompt or validate a saved LLM response against structured criteria. Human-in-the-loop: the CLI never invokes an LLM itself.
eidolons canary <name> # prompt mode
eidolons canary <name> --validate <file> # validate mode
eidolons canary --list # list mode
eidolons canary --memory # memory round-trip mode
| Mode | Invocation | What it does |
|---|---|---|
| prompt | eidolons canary <name> |
Print mission prompt + expected output shape + validation criteria |
| validate | eidolons canary <name> --validate <file> |
Check saved LLM output against mission criteria |
| list | eidolons canary --list |
Scan cache; report mission status per Eidolon (three states) |
| memory | eidolons canary --memory |
crystalium memory probe against the live project store: SKIP (crystalium not gated in / docker not on PATH). Otherwise runs a capability probe (commit --help) first. crystalium >= 1.7 (has the commit CLI verb): TRUE write→recall round trip — commits a uniquely-tokened canary record, recalls it back by that token, best-effort forgets it (cleanup failure never changes the verdict, WARN to stderr only) — PASS (record found on recall) / FAIL (commit refused, or committed but not recallable). crystalium < 1.7 (capability probe fails): falls back to the original recall-only liveness check — PASS (records returned) / INCONCLUSIVE (reachable, 0 records) / FAIL (unreachable) — and the PASS/INCONCLUSIVE text says explicitly it's liveness-only. Deliberately not crystalium's own canary subcommand either way (that A/B-evals a fresh ephemeral store, a different question). Output always states exactly what is and isn't checked. |
| Flag | Default | Behaviour |
|---|---|---|
--validate <file> |
unset | Switches to validate mode; file must exist and be readable |
--list |
unset | Switches to list mode |
--mission <id> |
first in file | Select a non-default mission by ID |
--json |
false | Emit machine-readable JSON on stdout; suppress human output |
-h, --help |
— | Print usage; exit 0 |
| Code | Meaning |
|---|---|
0 |
Prompt printed, OR validation all PASS/INCONCLUSIVE, OR list printed |
1 |
Validation had ≥1 FAIL criterion |
2 |
Misuse: unknown name, missing file, unknown flag |
Each criterion in evals/canary-missions.md follows - <SEVERITY> <verb>: <argument>:
| Severity | On mismatch |
|---|---|
MUST |
FAIL → exit 1 |
SHOULD |
Downgraded to INCONCLUSIVE → never causes exit 1 |
Four recognized verbs:
| Verb | Argument | Check |
|---|---|---|
contain heading: <text> |
exact markdown heading | grep -Fxq against lines stripped of leading whitespace |
contain phrase: <regex> |
extended regex | grep -Eq against full output |
mention paths: <p1>, <p2>, ... |
comma-separated path tokens | ALL tokens must appear (each grep -Fq) |
have token count between X and Y |
two integers | word-count × 4/3 ≥ X and ≤ Y |
Unrecognized verbs → INCONCLUSIVE ("unrecognized criterion"), never FAIL.
All --json emissions share:
{
"schema_version": "1.0",
"mode": "prompt|validate|list",
...
}Validate mode:
{
"schema_version": "1.0",
"mode": "validate",
"eidolon": "atlas",
"version": "1.7.1",
"mission_id": "default",
"summary": { "pass": 3, "fail": 0, "inconclusive": 0 },
"criteria": [
{ "criterion": "- MUST contain heading: ## Mission Brief",
"severity": "MUST", "result": "PASS", "reason": "" }
]
}The file lives in the per-version cache at ~/.eidolons/cache/<name>@<version>/evals/canary-missions.md. It is not shipped to the installed project tree.
## Mission: default
### Prompt
<verbatim prompt to feed the LLM>
### Expected output shape
<prose description — human-readable, ignored by the validator>
### Validation criteria
- MUST contain heading: ## Mission Brief
- MUST contain phrase: FINDING-
- MUST mention paths: skills/abstract.md, skills/locate.md
- SHOULD have token count between 1000 and 3000Multiple missions per file are supported. The first ## Mission: heading is the default; --mission <id> selects others.
# Step 1: print the prompt
eidolons canary atlas
# Step 2: paste into your LLM (Claude Code, claude.ai, API, ...)
# and save the response to a file
# Step 3: validate
eidolons canary atlas --validate /path/to/response.md
- Missing
evals/canary-missions.md→ warn + exit 0 (soft; not every Eidolon has authored missions yet). canary --listis a fast cache inspection; it does not fetch. Absent cache →(cache not populated; run 'eidolons sync').canary --listuses three display states:✓(file exists AND ≥1## Mission: <id>heading parses),⚠(file exists but 0 DSL missions found — legacy format),·(noevals/canary-missions.mdat all). Summary line reports all three counts:N with parseable missions, N with file-only (legacy format), N with no file.--jsonlist output usesschema_version: "1.1"and per-memberstatusfield ("parsed"/"legacy"/"missing"). Summary fields areparsed,legacy,missing.- Requires
eidolons.lock. Runeidolons add <name>first if the member is not in the lock.
The (arm × suite) matrix runner and its regression tracker — the H-WIN measurement instrument.
eidolons eval swe --matrix evals/arms/h-win.json [--smoke] [--no-store] [--suite-file evals/kupo-keep-suite.yaml] [--k N]
eidolons eval baseline <suite> [--label <arm-label>] [--against <scorecard.json>]
- Matrix: runs the whole suite once per arm (
{label, fix_hook, env, control}— schemaschemas/eval-arms.schema.json); each arm re-invokes the untouched single-arm path as a child process. Emits one scorecard per arm (evals/results/<date>-<suite>-<label>.scorecard.json, schemaschemas/eval-scorecard.schema.json) plus<date>-<suite>-matrix.json(pairwise vs the firstcontrol: truearm: rate deltas, newly-resolved/regressed flips).--smoke= gold_fix plumbing validation (free; what CI runs; scorecards recordharness.smoke: trueand are never capability claims).--no-storeskips persistence.EIDOLONS_EVAL_RESULTS_DIRoverrides the store location. Hooks receiveEIDOLONS_EVAL_TASK_BRIEF+ the arm's env (EIDOLONS_EVAL_MODEL,EIDOLONS_SANDBOX_MODEL_TIER). - Baseline: mechanical jq diff of the two most recent scorecards for (suite, label) — or latest vs
--against. Exit0clean ·5regression (rate drop or any resolved→unresolved flip) ·1misuse. - Reference arms:
evals/arms/h-win.jsonpins the honest headline comparison — (light-tier model + system discipline,keep-system.sh) vs (standard-tier model + bare prompt,keep-bare.sh). Hook prompts are versioned artifacts; changing them invalidates baselines. - CI:
.github/workflows/live-eval.yml— weekly cron runs--matrix --smoke; live billed runs requirevars.EIDOLONS_LIVE_EVAL_ENABLED == 'true'ANDANTHROPIC_API_KEY, else the job degrades to smoke and says why.
eidolons canary --host <claude-code|codex|copilot|cursor|opencode> # one host
eidolons canary --all-hosts # all five
Per-host effective-tier canary: probes the actual host surfaces (shims present+executable, settings/hooks wired, strict recipes) against the lockfile's harness.hosts_wired/strict expectations — PASS (reality matches lock) / FAIL (lock claims what reality doesn't back, with reasons) / SKIP (host not wired). Same probes as doctor D12, but per-host verdicts instead of one aggregate.
A/B behavioural instrument: does the advisory harness injection actually change a host
LLM's delegation behaviour? Measures routing compliance — whether the host dispatches to
the Eidolon the deterministic kernel would route to — with the harness wired (ARM A) versus
documentary cortex only (ARM B). Operationalizes the FORGE reversal gate
(DOSSIER-HARNESS-2026-06.md:106).
eidolons eval compliance --smoke --json # free pipeline self-test (fake driver)
eidolons eval compliance --dry-run --k 2 --arm both # print the billed-session cost, stop
# live (billed) — see .spectra/harness-mechanization/runbook-compliance.md:
EIDOLONS_COMPLIANCE_NO_LIVE= eidolons eval compliance --arm both --k 2 --yes --json| Flag | Meaning |
|---|---|
--smoke |
Run the whole pipeline against a canned fake driver. No model, no billing. CI default. |
--dry-run |
Print the COST: n_prompts × arms × k session count and exit 0 without calling a driver. |
--yes |
Required to start a live (billed) run. Without it, a non-smoke/non-dry run dies with the cost message. |
--arm A|B|both |
Which arm(s) to run (default both). A = harness wired; B = prose cortex only. |
--k N |
Repeats per prompt (default 1). Use ≥2 for any headline claim — k=1 is noise. |
--driver '<cmd>' |
Substitute the default claude -p driver. The command gets the prompt on argv and stdin and must emit the host stream on stdout. |
--model <m> |
Model for the default driver (default sonnet). |
--max-turns N |
Cap per session (default 3 — we measure whether an early Task dispatch fires, not task completion). |
--suite-file <path> |
Prompt suite (default evals/compliance-suite.yaml). |
--min N / --gate |
Exit 1 if ARM-A correct_target_rate is below N percent / below the 80% FORGE threshold. |
--keep / --capture-sample |
Keep fixtures / save the raw stream for parser reconciliation. |
--json |
Emit the machine-readable scorecard on stdout. |
Per arm: delegation_rate (any roster Task dispatched), correct_target_rate (first dispatch
matches the kernel's selected/chain — the gate metric), control_pass_rate (control
prompts correctly get NO dispatch), stability_passk, and a per-class breakdown. Top level:
delta (A − B, the isolated harness effect) and gate (verdict vs 80% + reversal action).
The instrument never bills from tests. eval_compliance.sh refuses the default claude
driver when EIDOLONS_COMPLIANCE_NO_LIVE=1 is set; the bats setup() exports it, so the
suite cannot spawn a billed session even if a test omits --smoke. Clear the variable only
for a deliberate, supervised live run (runbook). A custom --driver bypasses this net and
owns its own cost gating.
Inspect and control the nexus roster channel — the split between the pinned CLI version and the floating roster data.
eidolons nexus <subcommand> [options]
Force a roster data refresh now. Fetches only the data-layer paths
(roster/, EIDOLONS.md, methodology/cortex/) from the channel ref, leaving
the CLI code (cli/, schemas/, VERSION) pinned at the installed tag.
Honors skip-guards: no-op (prints "skipped") when EIDOLONS_NEXUS is set or
EIDOLONS_SKIP_REFRESH=1. Non-fatal when offline. Exit 0 always.
| Flag | Purpose |
|---|---|
--quiet |
Suppress informational output (only errors go to stderr). |
Get or set the roster channel (the ~/.eidolons/nexus/.roster_ref sidecar).
- No arg: print the current channel. Exit 0.
- With
<ref>: set the roster channel. Accepted values:main— track themainbranch (default).stable— magic token: at each refresh, resolves to the latest published release tag vianexus_latest_tag. Useful for "track releases, not main". If offline at refresh time, the refresh is skipped non-fatally.<tag>e.g.v1.16.0— freeze to a specific tag.<sha>— pin to a specific commit.<branch>— track any remote branch.
| Exit code | Meaning |
|---|---|
| 0 | Success |
| 2 | Empty / whitespace-only arg |
Read-only split report: CLI version/ref and roster channel/effective/freshness.
CLI
version: 1.16.0
ref: v1.16.0
commit: a3b4c5d
ROSTER
channel: main (effective: main)
cache HEAD: f9e8d7c
upstream: f9e8d7c | unreachable
freshness: up-to-date | behind (run: eidolons nexus refresh) | unknown (offline)
Upstream probe is gated by skip-guards (offline-safe). Exit 0 always.
The CLI code is pinned at .install_ref (updated only by eidolons upgrade self). The roster catalogue floats at .roster_ref (updated by
nexus_refresh on every sync/init/upgrade/mcp install/mcp upgrade).
Per-member integrity (commit/tree/archive SHA) is still verified at install/upgrade time — floating the catalogue only changes which pins are visible, never their verification. Users who need a fully frozen catalogue (reproducible builds, air-gap) run:
eidolons nexus channel stable # track latest release tag
eidolons nexus channel v1.16.0 # freeze to exact tag
export EIDOLONS_SKIP_REFRESH=1 # disable all auto-refreshSurface and apply upgrades for installed members.
eidolons upgrade [TARGET] [OPTIONS] # default: project scope (members)
eidolons upgrade --check [SCOPE] [TARGET] [--json] # read-only diff
eidolons upgrade --system [OPTIONS] # nexus only (git fetch + reset --hard)
eidolons upgrade --project [TARGET] [OPTIONS] # explicit project scope
eidolons upgrade --all [OPTIONS] # nexus then members
| Flag / arg | Purpose |
|---|---|
TARGET |
Member name or comma-separated list (mutually exclusive with --system / --all). |
--check |
Read-only diff: prints nexus and member upgrade availability, no disk writes. Pair with --system or --project to narrow the report. |
--system |
Upgrade only the nexus at ~/.eidolons/nexus (git fetch + reset --hard). For an atomic, integrity-verified self-upgrade, use eidolons upgrade self instead. |
--project |
Operate on cwd members. Equivalent to bare eidolons upgrade when given alone; useful for explicit symmetry with --system and for narrowing --check. |
--all |
Upgrade nexus first (must succeed), then members. Equivalent to --system --project. |
--json |
Combine with --check for machine-readable output (banner stays on stderr). |
--yes, -y |
Skip the confirmation prompt before mutating. |
--non-interactive |
Fail on prompts. Mutating runs require --yes. |
--dry-run |
Print plan without fetching or invoking any per-Eidolon install.sh. |
Pin policy: member constraints in eidolons.yaml are respected. A roster versions.latest that exceeds the constraint is reported as pinned-out and skipped — upgrade never auto-edits constraints.
Idempotency: a second run with no roster change reports "all members up-to-date" and leaves eidolons.lock mtime untouched.
Network failure: --check degrades gracefully (10s timeout on the nexus probe; member rows are purely local). Mutating runs fail per-member; the final exit code is 1 if any member upgrade failed. eidolons upgrade --system exits 1 if the nexus fetch fails (state is left untouched).
Statuses (--check): up-to-date, upgrade available, pinned-out, not-installed.
Upgrade the nexus CLI itself. Atomic, integrity-verified, rollback-safe.
eidolons upgrade self # upgrade to latest stable
eidolons upgrade self --ref vX.Y.Z # pin to a specific tag or commit
eidolons upgrade self --check # read-only: show what would change
eidolons upgrade self --rollback # revert to nexus.prev
eidolons upgrade self --force # skip dirty-tree and downgrade guards
eidolons upgrade self --non-interactive # fail on any prompt (for CI)
| Flag | Purpose |
|---|---|
--ref REF |
Specific git tag, branch, or commit SHA to upgrade to. Default: latest stable tag. |
--check |
Read-only mode: prints the current version, latest available, and upgrade plan. No disk writes. |
--rollback |
Swap ~/.eidolons/nexus.prev back into place. Only one previous version is retained. Exit 7 if no nexus.prev exists. |
--force |
Skip the dirty-tree guard and downgrade confirmation. Required when the current nexus has uncommitted changes. |
--non-interactive |
Fail instead of prompting (e.g. downgrade confirmation). Safe for CI use. |
How it works. upgrade self never modifies your current install until it is safe to do so:
- Resolves the target ref (default: latest stable tag via
git ls-remote). - Checks whether current version already matches — exits 0 (no-op) if so.
- Clones the target into
~/.eidolons/nexus.new/. - Verifies integrity: commit SHA, Git tree SHA, and archive SHA-256 all match
nexus.versions.releases.<v>inroster/index.yaml. Exit 5 on mismatch (unless the release block contains placeholder values, which is the bootstrap-window sentinel). - Runs a smoke test:
bash ~/.eidolons/nexus.new/cli/eidolons --version --quietexits 0. Exit 6 on failure. - Atomically swaps:
~/.eidolons/nexus→~/.eidolons/nexus.prev~/.eidolons/nexus.new→~/.eidolons/nexus
- The symlink at
~/.local/bin/eidolonsis unchanged — it already points at~/.eidolons/nexus/cli/eidolons.
On any failure before step 6, ~/.eidolons/nexus.new is removed and the current install is untouched.
Downgrade detection. If --ref targets a version older than the current install, the command warns and requires explicit confirmation (or --force / --non-interactive with --force).
Dirty-tree guard. If the current nexus directory has uncommitted changes (common when working directly from a checkout), the command refuses to proceed unless --force is passed.
Exit codes.
| Code | Meaning |
|---|---|
| 0 | Success (or already up-to-date) |
| 1 | Generic failure (details on stderr) |
| 2 | Already at the requested ref (no-op, same as 0 for no-op check) |
| 4 | Network error — could not reach upstream |
| 5 | Integrity verification failed |
| 6 | Smoke test failed on the new nexus |
| 7 | Rollback requested but no nexus.prev exists |
Maintainer-only. One-touch dispatch of an upstream Eidolon's Release
workflow followed by this nexus's Roster Intake. Replaces the manual two-step
gh workflow run chain.
eidolons release <eidolon> <version> [OPTIONS]
Behaviour.
- Validates SemVer (
X.Y.Z[-pre][+build]) and theghCLI version (requiresgh >= 2.20.0forgh pr merge --autosupport). - Resolves the Eidolon from the roster; checks
gh authscope per repo (Rynaro/<EIDOLON>andRynaro/eidolons); confirms the upstreamrelease.ymlworkflow file exists. - Version-precedence guard: rejects when the requested version equals the
roster
versions.latest(use--resume) or is older (use--force). - Dispatches
Release <DISPLAY>on the upstream repo viagh workflow run(skipped when--resumeand the tag already exists). - Polls
gh release view v<version>on the upstream repo until the tag appears or--release-timeoutelapses. - Dispatches
Roster IntakeonRynaro/eidolonswith the resolved eidolon + version inputs. - Polls
gh pr listfor the resultingcodex/roster-<name>-<v>branch until the PR opens or--intake-timeoutelapses. - Prints the PR URL, auto-merge status, and a follow-up
gh pr checkscommand. Hints the consumer to runeidolons upgrade <name>.
Flags.
| Flag | Default | Purpose |
|---|---|---|
--check |
— | Dry-run: validate plan only; print what would dispatch; no side effects. |
--resume |
— | Skip Release dispatch when the upstream tag already exists. Use after a partial completion. |
--force |
— | Allow version equal to or less than the current roster latest (intentional rollback path). |
--auto-merge |
— | Pass-through hint to Roster Intake (auto-merge is the default for routine bumps anyway). |
--yes, -y |
— | Skip the interactive confirmation prompt. Required with --non-interactive for mutating runs. |
--non-interactive |
— | Fail on prompts. Combine with --yes for unattended use (CI). |
--release-timeout=N |
600 | Seconds to wait for the upstream tag to appear. |
--intake-timeout=N |
300 | Seconds to wait for the Roster Intake PR to open. |
-h, --help |
— | Print usage and exit 0. |
Safety properties.
- Idempotent: re-running with
--resumeafter a partial completion never re-dispatches a workflow that already produced its tag. - No mutating action runs before validation completes (auth, version precedence, workflow existence).
- All log output to stderr; stdout reserved for capturable values (PR URL on success).
- Bash 3.2 safe.
Exit codes.
| Code | Meaning |
|---|---|
| 0 | Success — tag landed, intake dispatched, PR URL printed |
| 1 | Generic failure (details on stderr) |
| 2 | Usage error or validation failure (bad SemVer, unknown eidolon, version not ahead, missing flags) |
| 4 | Network/timeout — upstream tag never appeared, or intake PR never opened |
| 5 | Dispatch failure — gh workflow run returned non-zero |
Examples.
eidolons release atlas 1.4.0 # interactive prompt
eidolons release atlas 1.4.0 --yes # skip prompt
eidolons release atlas 1.4.0 --check # dry-run
eidolons release atlas 1.4.0 --resume # tag already landed
eidolons release atlas 1.4.0 --release-timeout=120 # short timeoutCompanion automation. Routine roster bumps that pass attestation
verification + required status checks now auto-merge once Roster Intake
opens the PR. First-shipped Eidolon transitions stay DRAFT for explicit
review. See docs/release-integrity.md § "Auto-merge of routine roster
bumps".
Unified MCP server store. See docs/mcp.md for the full reference.
eidolons mcp list # browse catalogue + installed status
eidolons mcp show <name> # full detail for one MCP
eidolons mcp install <name>[@<ver>] # install at pins.stable or explicit version
eidolons mcp refresh <name> # re-fetch artefact (image pull / binary)
eidolons mcp uninstall <name> # remove from this project
eidolons mcp upgrade [<name>|--all] # upgrade to catalogue stable
eidolons mcp sync # reconcile eidolons.yaml mcps: block
eidolons mcp health [<name>|--all] # run health probes; exit code always 0
eidolons mcp run <name> [<args>] # pass-through to binary MCP (junction only in v1.3)
| Variable | Purpose |
|---|---|
EIDOLONS_SUPPRESS_DEPRECATED=1 |
Silence DEPRECATED: lines from legacy verbs |
These still work but emit one DEPRECATED: line to stderr on every invocation.
Set EIDOLONS_SUPPRESS_DEPRECATED=1 to suppress.
| Legacy verb | Replacement |
|---|---|
eidolons mcp atlas-aci [--force] |
eidolons mcp install atlas-aci [--force] |
eidolons mcp atlas-aci pull [...] |
eidolons mcp refresh atlas-aci [...] |
eidolons harness install [ver] |
eidolons mcp install junction[@ver] |
eidolons harness up |
eidolons mcp health junction |
eidolons harness verify [args] |
eidolons mcp run junction verify [args] |
eidolons harness uninstall |
eidolons mcp uninstall junction |
eidolons <eidolon> <subcommand> [args...]
eidolons <eidolon> --help
Runs a subcommand shipped by an installed Eidolon. The nexus CLI resolves:
.eidolons/<eidolon>/commands/<subcommand>.shin the current project (preferred)~/.eidolons/cache/<eidolon>@<version>/commands/<subcommand>.sh(fallback)
The dispatcher passes all remaining args to the script and executes it with cwd set to the project root — same convention as sync and doctor.
To expose eidolons <eidolon> <sub>, ship a bash script at commands/<sub>.sh in the Eidolon's source repo and have the per-Eidolon install.sh copy commands/*.sh into <TARGET>/commands/. The script should:
- Read
cwdas the consumer project root (don'tcdelsewhere unless deliberate). - Source its own helpers / execute its own logic; the nexus doesn't inject anything.
- Exit non-zero on failure; output clear error messages to stderr.
The nexus dispatcher adds no contract beyond "be a bash script that does what you promise".
eidolons spectra --help # list SPECTRA's subcommands
eidolons spectra fit # run SPECTRA's project-fit tool
eidolons spectra fit /path/to/other # pass args throughSee atlas-aci.md for the atlas-aci MCP integration
command: prereqs, host coverage, exit codes, and the idempotency
contract. The command is opt-in and never invoked by init / sync.
The --host flag restricts wiring to a single host. Allowed values:
claude-code, cursor, copilot, codex. Omit --host to target
all MCP-capable hosts detected in the project.
Bootstrap pre-flight: eidolons mcp atlas-aci (scaffold) aborts with an
actionable error if DEFAULT_IMAGE_DIGEST is still the all-zeros placeholder
value. This prevents misconfigured .mcp.json files from reaching users before
the first real ghcr.io release has been pinned by a maintainer.
eidolons mcp atlas-aci pull [OPTIONS]
| Flag | Default | Purpose |
|---|---|---|
--build-locally |
off | Build the image locally from the upstream git source instead of pulling from ghcr.io. Use in air-gap, restricted-network, or registry-outage scenarios. |
--git-ref REF |
main |
Git ref (branch, tag, or SHA) to build from when --build-locally is used. Paired with --build-locally; no effect otherwise. |
--image-digest DIGEST |
DEFAULT_IMAGE_DIGEST |
Override the pinned digest. Accepts the bare sha256:<hex> form. Bypasses the bootstrap pre-flight check (use to adopt a locally-built tag or a different registry digest). |
--build-locally trade-off: the locally-built image is tagged
ghcr.io/rynaro/atlas-aci:locally-built-<timestamp> and cannot match the
upstream digest pin. docker run ghcr.io/rynaro/atlas-aci@sha256:<digest>
will not resolve to it — pass --image-digest with the local tag to use it.
See atlas-aci.md §Image distribution for the
full runbook.
Bootstrap pre-flight on pull: eidolons mcp atlas-aci pull also refuses to
run when DEFAULT_IMAGE_DIGEST is the all-zeros placeholder unless --image-digest
is explicitly supplied (which bypasses the guard). The error message names the
two recovery options: wait for the first ghcr.io release, or use --build-locally.
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Operation failed (details printed) |
2 |
Invalid arguments |
3 |
Existing install conflicts with requested action (use --force) |
4 |
Token budget / conformance violation (from per-Eidolon install) |
The CLI is designed to compose:
# Provision in CI
eidolons init --preset pipeline --non-interactive
# Verify in CI
eidolons doctor || exit 1
# Pin explicitly in a Dockerfile
RUN curl -sSL https://raw.githubusercontent.com/Rynaro/eidolons/v1.0.0/cli/install.sh | bash
RUN eidolons init --preset pipeline --non-interactive