Install any skill in seconds. Free to start, no credit card required.
Get Started Free →First-touch experience for new Ouroboros users
.claude/skills/q00-welcome/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 177% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 225% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 301% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 334% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 357% | 0% |
Interactive onboarding for new Ouroboros users.
/ouroboros:welcome # First-time or update onboarding
/ouroboros:welcome --skip # Skip welcome, mark as shown
/ouroboros:welcome --force # Force re-run welcome even if shownWhen this skill is invoked, follow this flow:
Before running any shell snippet below, define this resolver in the same shell. It accepts only Python 3.12 or newer, prefers python3 and then python, and uses uv as the final fallback. Call ouroboros_python directly and quote every argument passed to it; the function preserves arguments and heredoc/stdin input. Only the probe and child interpreter discard inherited CPython path-selection overrides; the caller shell keeps its environment unchanged.
<!-- ouroboros-python-resolver:start -->
bashouroboros_python() { if command -v python3 >/dev/null 2>&1 && (unset PYTHONHOME PYTHONPATH PYTHONPLATLIBDIR PYTHONEXECUTABLE __PYVENV_LAUNCHER__; command python3 -c 'import sys; raise SystemExit(sys.version_info < (3, 12))') >/dev/null 2>&1 then (unset PYTHONHOME PYTHONPATH PYTHONPLATLIBDIR PYTHONEXECUTABLE __PYVENV_LAUNCHER__; command python3 "$@") return fi if command -v python >/dev/null 2>&1 && (unset PYTHONHOME PYTHONPATH PYTHONPLATLIBDIR PYTHONEXECUTABLE __PYVENV_LAUNCHER__; command python -c 'import sys; raise SystemExit(sys.version_info < (3, 12))') >/dev/null 2>&1 then (unset PYTHONHOME PYTHONPATH PYTHONPLATLIBDIR PYTHONEXECUTABLE __PYVENV_LAUNCHER__; command python "$@") return fi if command -v uv >/dev/null 2>&1; then (unset PYTHONHOME PYTHONPATH PYTHONPLATLIBDIR PYTHONEXECUTABLE __PYVENV_LAUNCHER__; command uv run --no-project --quiet --python '>=3.12' python "$@") return fi printf '%s\n' 'Ouroboros skills require Python >= 3.12 or uv on PATH.' >&2 return 127 }
<!-- ouroboros-python-resolver:end -->
First, check ~/.ouroboros/prefs.json for welcomeCompleted. For upgrades from older releases, also treat legacy welcomeShown: true as completed so the welcome prompt does not reappear forever:
bashPREFFILE="$HOME/.ouroboros/prefs.json" if [ -f "$PREFFILE" ]; then WELCOME_COMPLETED=$(ouroboros_python - <<'PY' import json, os path = os.path.expanduser('~/.ouroboros/prefs.json') try: prefs = json.load(open(path, encoding='utf-8')) except Exception: prefs = {} if not isinstance(prefs, dict): prefs = {} print(prefs.get('welcomeCompleted') or ('legacy-welcomeShown' if prefs.get('welcomeShown') else '')) PY ) WELCOME_VERSION=$(ouroboros_python - <<'PY' import json, os path = os.path.expanduser('~/.ouroboros/prefs.json') try: prefs = json.load(open(path, encoding='utf-8')) except Exception: prefs = {} if not isinstance(prefs, dict): prefs = {} print(prefs.get('welcomeVersion') or '') PY ) if [ -n "$WELCOME_COMPLETED" ] && [ "$WELCOME_COMPLETED" != "null" ]; then ALREADY_COMPLETED="true" fi fi
Before honoring that completion marker, determine whether setup is ready. A previously completed welcome must never hide the setup gate from a user who chose 나중에 or whose setup was later removed:
bashif ouroboros_python - "$HOME/.ouroboros/config.yaml" <<'PY' from __future__ import annotations import sys from pathlib import Path try: import yaml except ModuleNotFoundError: yaml = None config_path = Path(sys.argv[1]) def yaml_mapping(source: str) -> dict[str, dict[str, str]]: """Read the top-level mapping scalars this readiness gate owns.""" if yaml is not None: loaded = yaml.safe_load(source) or {} return loaded if isinstance(loaded, dict) else {} parsed: dict[str, dict[str, str]] = {} section: str | None = None def scalar_value(raw: str) -> str: return raw.strip().split(" #", 1)[0].strip().rstrip(",}").strip().strip("'\"") def flow_mapping(raw: str) -> dict[str, str]: value = raw.strip().split(" #", 1)[0].strip() if not (value.startswith("{") and value.endswith("}")): return {} fields: dict[str, str] = {} for part in value[1:-1].split(","): key, separator, field_value = part.partition(":") if separator: fields[key.strip().strip("'\"")] = scalar_value(field_value) return fields for raw_line in source.splitlines(): if not raw_line.strip() or raw_line.lstrip().startswith("#"): continue indent = len(raw_line) - len(raw_line.lstrip()) key, separator, raw_value = raw_line.strip().partition(":") if not separator: continue if indent == 0: section = key.strip("'\"") parsed[section] = flow_mapping(raw_value) elif section is not None: parsed.setdefault(section, {})[key.strip("'\"")] = scalar_value(raw_value) return parsed try: config = yaml_mapping(config_path.read_text(encoding="utf-8")) except (OSError, ValueError): raise SystemExit(1) orchestrator = config.get("orchestrator") if isinstance(config, dict) else None llm = config.get("llm") if isinstance(config, dict) else None # Existing YAML form: runtime_backend: claude. Parsing avoids assuming its order. # The marketplace plugin owns its MCP capability. Host-owned # ~/.claude/mcp.json is intentionally not part of SDK setup readiness. ready = ( isinstance(orchestrator, dict) and orchestrator.get("runtime_backend") in {"claude", "claude_mcp"} and isinstance(llm, dict) and llm.get("backend") == "claude" ) raise SystemExit(0 if ready else 1) PY then SETUP_READY="true" fi
If ALREADY_COMPLETED is true, SETUP_READY is true, AND no --force flag:
Use AskUserQuestion:
json{ "questions": [{ "question": "Ouroboros welcome was already completed on $WELCOME_COMPLETED. What would you like to do?", "header": "Welcome", "options": [ { "label": "Skip", "description": "Continue to work (recommended)" }, { "label": "Re-run welcome", "description": "Go through the interactive onboarding again" } ], "multiSelect": false }] }
If the welcome was completed but SETUP_READY is not true, bypass this completion prompt and continue to the Setup Gate below.
If --skip flag present:
welcomeShown: true, welcomeCompleted: <current timestamp>, and welcomeVersion into ~/.ouroboros/prefs.json without deleting existing keys:bash ouroboros_python - <<'PY' import json, os from datetime import UTC, datetime path = os.path.expanduser('~/.ouroboros/prefs.json') os.makedirs(os.path.dirname(path), exist_ok=True) try: with open(path, encoding='utf-8') as f: prefs = json.load(f) if not isinstance(prefs, dict): prefs = {} except Exception: prefs = {} prefs.update({ 'welcomeShown': True, 'welcomeCompleted': datetime.now(UTC).isoformat(), 'welcomeVersion': '0.36.0', }) with open(path, 'w', encoding='utf-8') as f: json.dump(prefs, f, indent=2) f.write('\n') PY
Ouroboros welcome skipped. Run /ouroboros:welcome --force to re-run onboarding.
Before showing the welcome banner, check whether Ouroboros has been prepared on this machine:
bashif ouroboros_python - "$HOME/.ouroboros/config.yaml" <<'PY' from __future__ import annotations import sys from pathlib import Path try: import yaml except ModuleNotFoundError: yaml = None config_path = Path(sys.argv[1]) def yaml_mapping(source: str) -> dict[str, dict[str, str]]: """Read only the top-level mapping scalars owned by this readiness gate.""" if yaml is not None: loaded = yaml.safe_load(source) or {} return loaded if isinstance(loaded, dict) else {} parsed: dict[str, dict[str, str]] = {} section: str | None = None def scalar_value(raw: str) -> str: return raw.strip().split(" #", 1)[0].strip().rstrip(",}").strip().strip("'\"") def flow_mapping(raw: str) -> dict[str, str]: value = raw.strip().split(" #", 1)[0].strip() if not (value.startswith("{") and value.endswith("}")): return {} fields: dict[str, str] = {} for part in value[1:-1].split(","): key, separator, field_value = part.partition(":") if separator: fields[key.strip().strip("'\"")] = scalar_value(field_value) return fields for raw_line in source.splitlines(): if not raw_line.strip() or raw_line.lstrip().startswith("#"): continue indent = len(raw_line) - len(raw_line.lstrip()) key, separator, raw_value = raw_line.strip().partition(":") if not separator: continue if indent == 0: section = key.strip("'\"") parsed[section] = flow_mapping(raw_value) elif section is not None: parsed.setdefault(section, {})[key.strip("'\"")] = scalar_value(raw_value) return parsed try: config = yaml_mapping(config_path.read_text(encoding="utf-8")) except (OSError, ValueError): raise SystemExit(1) orchestrator = config.get("orchestrator") if isinstance(config, dict) else None llm = config.get("llm") if isinstance(config, dict) else None # Existing YAML form: runtime_backend: claude. Parsing avoids assuming its order. # The marketplace plugin owns its MCP capability. Host-owned # ~/.claude/mcp.json is intentionally not part of SDK setup readiness. ready = ( isinstance(orchestrator, dict) and orchestrator.get("runtime_backend") in {"claude", "claude_mcp"} and isinstance(llm, dict) and llm.get("backend") == "claude" ) raise SystemExit(0 if ready else 1) PY then echo "SETUP_READY" else echo "SETUP_REQUIRED" fi
If setup is required, ask one concise question in the user's language. For a Korean conversation, use:
json{ "questions": [{ "question": "Ouroboros를 처음 사용하시네요. 시작하기 전에 실행 환경을 설정할까요?", "header": "Ouroboros 시작하기", "options": [ { "label": "설정하고 시작하기 (권장)", "description": "한 번만 설정하면 바로 사용할 수 있어요" }, { "label": "나중에", "description": "지금은 기본 안내만 보고 나중에 설정할게요" } ], "multiSelect": false }] }
../setup/SKILL.md. Do not ask the userto copy a command when the current host can run it.
execution features are ready.
After successful setup, ../setup/SKILL.md presents the model choice. Do not repeat it here; continue to Step 1 after the setup skill returns.
Do not show this gate again once the Claude runtime and LLM backend are ready. The normal settings UI remains available later through ooo config, so a model choice made now is never permanent.
Display:
Welcome to Ouroboros!
The serpent that eats itself -- better every loop.
Most AI coding fails at the input, not the output.
Ouroboros fixes this by exposing hidden assumptions
BEFORE any code is written.
Interview -> Seed -> Execute -> Evaluate
^ |
+---- Evolutionary Loop -----+AskUserQuestion:
json{ "questions": [{ "question": "What brings you to Ouroboros?", "header": "Welcome", "options": [ { "label": "New project idea", "description": "I have a vague idea and want to crystallize it into a clear spec" }, { "label": "Tired of rewriting prompts", "description": "AI keeps building the wrong thing because my requirements are unclear" }, { "label": "Just exploring", "description": "Heard about Ouroboros and want to see what it does" } ], "multiSelect": false }] }
Give brief personalized response (1-2 sentences) based on choice.
Ordinary Claude setup uses the default [claude] Agent SDK profile on MCP 1.x. It intentionally leaves host-owned ~/.claude/mcp.json untouched; do not inspect or mutate that file as an onboarding health check. The dependency-free worker is the explicit [claude-cli] profile used by an isolated MCP 2 process.
If the active runtime does not expose Ouroboros MCP tools, AskUserQuestion:
json{ "questions": [{ "question": "Advanced MCP workflows require a host-managed MCP 2 launcher. What would you like to do?", "header": "Runtime", "options": [ { "label": "Continue native (Recommended)", "description": "Use Claude-native interview, seed, evaluate, and unstuck workflows" }, { "label": "Show MCP setup", "description": "See supported Codex, OpenCode, Kiro, Copilot, or Hermes setup commands" } ], "multiSelect": false }] }
supported host setup owns the isolated [mcp] launcher. Never combine [claude-sdk] with [mcp]. Then continue to Step 4.
Available Commands:
+---------------------------------------------------+
| Command | What It Does |
|-----------------|----------------------------------|
| ooo interview | Socratic Q&A -- expose hidden |
| | assumptions in your requirements |
| ooo seed | Crystallize answers into spec |
| ooo run | Execute with visual TUI |
| ooo evaluate | 3-stage verification |
| ooo unstuck | Lateral thinking when stuck |
| ooo config | Settings GUI: agents & models |
| ooo help | Full command reference |
+---------------------------------------------------+AskUserQuestion:
json{ "questions": [{ "question": "What would you like to do first?", "header": "Get started", "options": [ { "label": "Start a project", "description": "Run a Socratic interview on your idea right now" }, { "label": "Try the tutorial", "description": "Interactive hands-on learning with a sample project" }, { "label": "Read the docs", "description": "Full command reference and architecture overview" } ], "multiSelect": false }] }
Based on choice:
../interview/SKILL.md../tutorial/SKILL.md../help/SKILL.mdCheck gh availability first:
bashgh auth status &>/dev/null && echo "GH_OK" || echo "GH_MISSING"
If GH_OK AND star_asked not true:
AskUserQuestion:
json{ "questions": [{ "question": "If you're enjoying Ouroboros, would you like to star it on GitHub?", "header": "Community", "options": [ { "label": "Star on GitHub", "description": "Takes 1 second -- helps the project grow" }, { "label": "Maybe later", "description": "Skip for now" } ], "multiSelect": false }] }
gh api -X PUT /user/starred/Q00/ouroboros~/.ouroboros/prefs.json without deleting existing keys. Set star_asked: true after either star prompt choice so the star prompt is not repeated:bash ouroboros_python - <<'PY' import json, os from datetime import UTC, datetime path = os.path.expanduser('~/.ouroboros/prefs.json') os.makedirs(os.path.dirname(path), exist_ok=True) try: with open(path, encoding='utf-8') as f: prefs = json.load(f) if not isinstance(prefs, dict): prefs = {} except Exception: prefs = {} prefs.update({ 'star_asked': True, 'welcomeShown': True, 'welcomeCompleted': datetime.now(UTC).isoformat(), 'welcomeVersion': '0.36.0', }) with open(path, 'w', encoding='utf-8') as f: json.dump(prefs, f, indent=2) f.write('\n') PY
If GH_MISSING or star_asked is true: Merge the welcome completion fields into ~/.ouroboros/prefs.json without deleting existing keys: bash ouroboros_python - <<'PY' import json, os from datetime import UTC, datetime path = os.path.expanduser('~/.ouroboros/prefs.json') os.makedirs(os.path.dirname(path), exist_ok=True) try: with open(path, encoding='utf-8') as f: prefs = json.load(f) if not isinstance(prefs, dict): prefs = {} except Exception: prefs = {} prefs.update({ 'welcomeShown': True, 'welcomeCompleted': datetime.now(UTC).isoformat(), 'welcomeVersion': '0.36.0', }) with open(path, 'w', encoding='utf-8') as f: json.dump(prefs, f, indent=2) f.write('\n') PY
Ouroboros Setup Complete!
MAGIC KEYWORDS (optional shortcuts):
Just include these naturally in your request:
| Keyword | Effect | Example |
|---------|--------|---------|
| interview | Socratic Q&A | "interview me about my app idea" |
| seed | Crystallize spec | "seed the requirements" |
| evaluate | 3-stage check | "evaluate this implementation" |
| stuck | Lateral thinking | "I'm stuck on the auth flow" |
REAL-TIME MONITORING (TUI):
When running ooo run or ooo evolve, open a separate terminal:
uvx --python '>=3.12' --from 'ouroboros-ai[tui]' ouroboros tui monitor
Press 1-4 to switch screens (Dashboard, Execution, Logs, Debug).
READY TO BUILD:
- ooo interview "your project idea"
- ooo tutorial # Interactive learning
- ooo help # Full reference~/.ouroboros/prefs.json:
json{ "welcomeShown": true, "welcomeCompleted": "2025-02-23T15:30:00+09:00", "welcomeVersion": "0.36.0", "star_asked": true }
Your final response MUST end with exactly one breadcrumb footer line:
◆ <current state> → next: <recommended action>Derive <current state> from live session state via ouroboros_session_status when that MCP projection is available; otherwise derive it from this skill's actual outcome. Never use a linear Step N of M footer because Ouroboros is an evolutionary loop. When the next action is genuinely a choice, list 2-3 honest options in the next: clause. The breadcrumb line must be the last line of the response.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 9,585 | 34,582 | +261% | 1 | 1 | 0% | 1,725 | 7,024 | +307% | 0 | 0 | — |
case-02 | fail→fail | 8,462 | 22,418 | +165% | 1 | 1 | 0% | 1,466 | 6,684 | +356% | 0 | 0 | — |
case-03 | fail→fail | 4,352 | 16,300 | +275% | 1 | 1 | 0% | 806 | 6,822 | +746% | 0 | 0 | — |
case-04 | fail→fail | 6,744 | 22,299 | +231% | 1 | 1 | 0% | 1,213 | 6,790 | +460% | 0 | 0 | — |
case-05 | pass→fail | 6,751 | 28,150 | +317% | 1 | 1 | 0% | 1,112 | 6,884 | +519% | 0 | 0 | — |
case-06 | fail→fail | 11,525 | 33,195 | +188% | 1 | 1 | 0% | 1,619 | 7,232 | +347% | 0 | 0 | — |
case-07 | fail→pass | 19,260 | 16,140 | -16% | 1 | 1 | 0% | 2,727 | 7,564 | +177% | 0 | 0 | — |
case-08 | pass→pass | 11,053 | 6,856 | -38% | 1 | 1 | 0% | 1,835 | 6,792 | +270% | 0 | 0 | — |
case-13 | fail→pass | 16,871 | 7,865 | -53% | 1 | 1 | 0% | 2,110 | 6,847 | +225% | 0 | 0 | — |
case-09 | fail→pass | 9,437 | 5,059 | -46% | 1 | 1 | 0% | 1,593 | 6,388 | +301% | 0 | 0 | — |
case-10 | fail→pass | 10,249 | 10,101 | -1% | 1 | 1 | 0% | 1,638 | 7,103 | +334% | 0 | 0 | — |
case-11 | fail→pass | 8,582 | 4,773 | -44% | 1 | 1 | 0% | 1,393 | 6,366 | +357% | 0 | 0 | — |
case-12 | pass→pass | 11,582 | 7,748 | -33% | 1 | 1 | 0% | 1,557 | 6,490 | +317% | 0 | 0 | — |
case-14 | fail→pass | 12,440 | 5,380 | -57% | 1 | 1 | 0% | 1,678 | 6,218 | +271% | 0 | 0 | — |
case-15 | fail→pass | 8,583 | 5,606 | -35% | 1 | 1 | 0% | 1,314 | 6,140 | +367% | 0 | 0 | — |
case-16 | fail→pass | 8,288 | 3,294 | -60% | 1 | 1 | 0% | 1,265 | 5,979 | +373% | 0 | 0 | — |
case-17 | fail→pass | 18,274 | 5,036 | -72% | 1 | 1 | 0% | 3,238 | 6,302 | +95% | 0 | 0 | — |
case-18 | fail→pass | 11,112 | 3,043 | -73% | 1 | 1 | 0% | 1,705 | 5,949 | +249% | 0 | 0 | — |
case-19 | fail→pass | 11,769 | 8,396 | -29% | 1 | 1 | 0% | 2,133 | 6,740 | +216% | 0 | 0 | — |
case-20 | fail→pass | 4,316 | 3,916 | -9% | 1 | 1 | 0% | 539 | 5,989 | +1011% | 0 | 0 | — |
case-21 | fail→pass | 13,108 | 5,431 | -59% | 1 | 1 | 0% | 1,796 | 6,175 | +244% | 0 | 0 | — |
case-22 | fail→pass | 6,932 | 6,198 | -11% | 1 | 1 | 0% | 1,105 | 6,541 | +492% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 16 counted toward the lift figure. The other 6 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +59 percentage points is the difference between those two pass rates over the 16 comparable cases. 3 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.