alive:demo -- generative demo-world skill
This is the router. Route on $ARGUMENTS[0] to a sibling .md file (when present) or to the interactive create flow. Six subcommands + an interactive default:
$ARGUMENTS[0] | Routes to | Status |
|---|---|---|
empty / create | Interactive: preset vs. custom; create.md for the custom path | wired |
list | list.md | wired |
activate <ref> | activate.md | wired |
deactivate | deactivate.md | wired |
delete <ref> | delete.md | wired |
status | status.md | wired |
reset | rebuilds demo-state.json (recovery from schema mismatch) | wired |
spike-test | retired (fn-2-2zz.1 spike) | removed below |
All five non-create sibling .md files (list.md, activate.md, deactivate.md, delete.md, status.md) invoke their CLI subcommand under $ALIVE_PLUGIN_ROOT/bin/alive demo <name> and print the CLI's rendered_block field verbatim. create.md is the custom-path orchestrator: it walks Stage 0 -> Stage 1 hand-off -> Stage 2/3/4/5 driver chain end-to-end. The interactive default below routes the human into either the preset path or create.md based on their pick.
The router emits bordered blocks INLINE as markdown — matches the convention in plugins/alive/skills/world/SKILL.md and plugins/alive/skills/save/SKILL.md. There is no Python render_block bridge: the LLM emits its own markdown; only the CLI uses lib.format_block (per the codex review which rejected forcing LLM formatting from Python).
User choices use AskUserQuestion, never inline 1. / 2. / 3. answer text. Numbered options inside a bordered block are advisory; the actual decision flows through the question tool.
State + paths (canonical)
| Concern | Path | Override? |
|---|---|---|
| Demo state file | ~/.config/alive/demo-state.json | NO. Tests set HOME to tmp_path. |
| Demo state lock | ~/.config/alive/.demo-state.lock | NO. |
| World-root pointer | ~/.config/alive/world-root | NO — owned by _world_root_io.write_world_root_file (#64). |
| Demo worlds + partials | ~/.alive-demos/ | YES via $ALIVE_DEMO_BASE_DIR. |
$ALIVE_DEMO_BASE_DIR is the only env override the demo skill reads. It controls where promoted worlds (<base>/wld_<ulid>/) and in-flight partials (<base>/wld_<ulid>.partial/) live. The state file and the world-root pointer stay canonical so the existing system-wide readers (_common.py, alive-common.sh, world/SKILL.md) keep working unchanged.
Router — interactive default (/alive:demo with no args)
When invoked with no subcommand, render this block inline and dispatch via AskUserQuestion:
╭─ 🐿️ /alive:demo
│
│ No demo subcommand given. Two paths:
│
│ 1. preset (sandbox-testing, deterministic, ~10s)
│ 2. custom (persona-driven, LLM-backed, ~1-2 min)
│
│ What would you like to do?
╰─
Then:
AskUserQuestionwith optionsPreset (sandbox-testing),Custom (persona-driven),Cancel.- On
Preset-> the "Preset path (sandbox testing)" section below; the squirrel callsalive demo preset run --preset realistic-seededafter the activation pre-check confirmation. - On
Custom-> jump intocreate.md(sibling of this file).create.mdowns the persona intake, partial-dir mint, Stage 0 driver loop, Stage 1 hand-off toanchor_confirm.md, and the Stage 2/3/4/5 driver chain end-to-end. - On
Cancel-> render the cancellation block and stop:
╭─ 🐿️ cancelled
│
│ No demo world created. Run /alive:demo again any time.
╰─
When routing to create.md, render this one-line cue inline before jumping in so the human sees the routing decision:
> routing to create.md (custom persona path)
Subcommand routing
The CLI is the only path that touches demo-state.json and the world-root pointer. Each subcommand below shells out to $ALIVE_PLUGIN_ROOT/bin/alive demo <name> and prints the CLI's rendered_block field verbatim. Sibling .md files carry the per-subcommand UX detail; SKILL.md keeps the per-subcommand entry points short.
list
Routes to list.md. Prints a 6-column bordered-block table of every promoted demo world plus partial generations. Active world is flagged with *active in the STATUS column.
$ALIVE_PLUGIN_ROOT/bin/alive demo list
See list.md for envelope shape and error handling.
activate <ref>
Routes to activate.md. Resolves <ref> via 3-step fallback (exact label, ULID prefix, ambiguous-match envelope), runs the activation pre-check, and (with --confirm) runs the tail of the Stage 5 transaction (build-log refresh + step 10 demo-state staging + step 11 world-root commit) against the existing world.
$ALIVE_PLUGIN_ROOT/bin/alive demo activate <ref>
$ALIVE_PLUGIN_ROOT/bin/alive demo activate <ref> --confirm
<ref> is the lowercase derived label (e.g. alex-boring-angel-investor) OR a ULID prefix of at least 3 characters (e.g. 01j5hk7y or wld_01j5hk7y).
The squirrel drives the picker on error.code == "ambiguous_ref": render the picker block from the envelope, dispatch AskUserQuestion, re-run with the picked ULID.
The squirrel drives confirmation on error.code == "needs_confirmation": render the warning block, dispatch AskUserQuestion (Activate anyway, Cancel), re-run with --confirm.
See activate.md for full flow and resolution rules.
deactivate
Routes to deactivate.md. Restores the world-root pointer to the cached previous_world_root value, then clears active_world and previous_world_root in demo-state.json.
$ALIVE_PLUGIN_ROOT/bin/alive demo deactivate
Three result branches: ok (pointer flipped, restart Claude Code), no_demo_active (nothing to do), and no_previous_world (cold demo; user must create another or set the pointer manually).
See deactivate.md for the cold-demo case and atomicity contract.
delete <ref>
Routes to delete.md. Resolves <ref> (same rules as activate), refuses on the currently-active world, and (with --confirm) calls shutil.rmtree. Without --confirm, the CLI returns error.code == "needs_confirmation" carrying an irreversibility surface so the squirrel dispatches AskUserQuestion before the destructive call.
$ALIVE_PLUGIN_ROOT/bin/alive demo delete <ref> # dry run; surfaces irreversibility block
$ALIVE_PLUGIN_ROOT/bin/alive demo delete <ref> --confirm # actually delete
Active worlds raise error.code == "refused_active"; the user must /alive:demo deactivate first.
See delete.md for the irreversibility contract.
status
Routes to status.md. Prints a 5-7 line bordered block summarising the active demo world, the cached previous world-root, and the next-step hint.
$ALIVE_PLUGIN_ROOT/bin/alive demo status
The CLI runs state.load_state() which executes the world-root self-heal -- demo-state converges to whatever ~/.config/alive/world-root actually points at. The status envelope is the canonical "what does the system think is active?" surface.
See status.md for output shapes and the self-heal contract.
reset
This subcommand is router-owned, not a CLI handler. When the human runs /alive:demo reset:
- Read
~/.config/alive/world-rootvia_world_root_io.read_world_root_file()to learn the live pointer (if any). - Atomically write a fresh
demo-state.jsonmatchingstate.default_state(). If the pointer names a directory previously promoted by the demo skill (recognizable by<world>/.alive/_demo-build-log.md), populateactive_worldfrom the build log. Otherwise leave it null. - Render this block:
╭─ 🐿️ demo state reset
│
│ demo-state.json rebuilt from world-root pointer.
│ Schema version: 0.1
│
│ active_world: <ulid|none>
╰─
Reset is the documented escape hatch when alive demo status raises schema_version_mismatch — the CLI's hint points here.
Schema-version mismatch UX
When `alive demo status