v0.3.0-rc.1: live skill registry + fast advisor scaffold #24

Closed
halofourteen wants to merge 1 commits from v0.3.0-rc.1 into main
halofourteen commented 2026-05-27 17:40:24 +03:00 (Migrated from github.com)

What and why

In v0.2.x, 47 of 47 Pi-extracted lessons had applied_count = 0. Why?
Pi (the coach LLM) routinely invented skill ids that don't exist —
relocate.surface, choose.safe.surface, survive.shelter,
gather.visible_log, tunnel-out (without recovery. prefix). The
dispatcher's normalisePreferSkill() had a fuzzy mapper but no live
registry to validate against, so these dead lessons sat in the DB
producing nothing.

This rc closes that loop at two boundaries:

  1. Write-time: Pi prompts (coach/postmortem.js, coach/reflect.js)
    now include the live skill registry as a system-prompt block with a
    "USE ONLY THESE, never invent" instruction. On insert, any
    prefer_skill / avoid_skill that isn't a real id or a known mode
    name gets dropped (warn-logged for visibility).
  2. Read-time: normalisePreferSkill() now returns null for
    unknown ids. Anything that slipped through write-time validation
    still can't dispatch a nonexistent skill.

It also lays the LLM substrate for the rest of v0.3.0
(needs-ladder integration in rc.2, awareness-driven advisor calls in rc.3).

Changes

  • runtime/skill-registry.js (new) — single source of truth wrapping
    skills/index.js. Exports listSkillIds, isRegistered,
    skillRegistryPrompt. No circular import risk.
  • runtime/llm/provider.js (new) — OpenAI-compatible chat client.
    Env-driven: PEPA_FAST_LLM_{BASE_URL,API_KEY,MODEL,TIMEOUT_MS}. Safe
    no-op when API_KEY is unset (so this PR is shippable disabled and
    flipped on later with one env var). Supports JSON-mode.
  • runtime/coach/fast-advisor.js (new) — tactical advisor tier.
    advise() returns {action: 'switch_skill'|'continue'|'wait', skillId?, rationale}. Not auto-triggered in rc.1 — scaffold only,
    wired into the reflex in rc.3.
  • runtime/coach/advice.jsnormalisePreferSkill() hardened to
    reject anything not in registry/mode-map. Warn line when a
    hallucinated id is dropped.
  • runtime/coach/postmortem.js, runtime/coach/reflect.js — embed
    skillRegistryPrompt() in their Pi prompts. Filter Pi's response at
    write time.

Configuration (when ready to flip on)

# TimeWeb (or any OpenAI-compatible endpoint)
export PEPA_FAST_LLM_BASE_URL="https://<endpoint>/v1"
export PEPA_FAST_LLM_API_KEY="<key>"
export PEPA_FAST_LLM_MODEL="gpt-5-mini"   # or whatever

Without the env vars, fast-advisor is a no-op and the bot runs
exactly as before — just with hallucinated lessons properly rejected.

Test plan

  • npm test — 279 green (was 257 on rc.3, +24 new):
    • 5 tests for skill-registry
    • 9 tests for llm/provider (incl. stubbed fetch + 401 path)
    • 10 tests for fast-advisor (incl. rejection of hallucinated skill)
    • advice.test.js regression: normalisePreferSkill("relocate.surface") → null
  • After merge: deploy and watch
    SELECT source, COUNT(*), SUM(applied_count > 0) FROM lessons GROUP BY source
    — Pi-coach / Pi-reflect applied count should start growing.

Followup (not in this PR)

  • rc.2: runtime/manifesto/needs.js (L0-L10 Maslow ladder) + curriculum
    integration. Bot pursues concrete intermediate goals (tools_wood →
    shelter_basic → tools_stone → ...) instead of "explore further".
  • rc.3: Event-driven awareness layer + AbortSignal plumbing into
    long-running skills, so the bot reacts to forced moves / HP plunges /
    hostile spawns within ~100ms instead of waiting for the next dispatch
    tick. Awareness layer is what auto-triggers fast-advisor.advise().

See dev/v0.3.0/PLAN.md for the full design and
dev/v0.3.0/STATUS.md for shipped/pending.

🤖 Generated with Claude Code

## What and why In v0.2.x, **47 of 47 Pi-extracted lessons** had `applied_count = 0`. Why? Pi (the coach LLM) routinely invented skill ids that don't exist — `relocate.surface`, `choose.safe.surface`, `survive.shelter`, `gather.visible_log`, `tunnel-out` (without `recovery.` prefix). The dispatcher's `normalisePreferSkill()` had a fuzzy mapper but no live registry to validate against, so these dead lessons sat in the DB producing nothing. This rc closes that loop at two boundaries: 1. **Write-time**: Pi prompts (`coach/postmortem.js`, `coach/reflect.js`) now include the live skill registry as a system-prompt block with a "USE ONLY THESE, never invent" instruction. On insert, any `prefer_skill` / `avoid_skill` that isn't a real id or a known mode name gets dropped (warn-logged for visibility). 2. **Read-time**: `normalisePreferSkill()` now returns `null` for unknown ids. Anything that slipped through write-time validation still can't dispatch a nonexistent skill. It also lays the **LLM substrate** for the rest of v0.3.0 (needs-ladder integration in rc.2, awareness-driven advisor calls in rc.3). ## Changes - `runtime/skill-registry.js` (new) — single source of truth wrapping `skills/index.js`. Exports `listSkillIds`, `isRegistered`, `skillRegistryPrompt`. No circular import risk. - `runtime/llm/provider.js` (new) — OpenAI-compatible chat client. Env-driven: `PEPA_FAST_LLM_{BASE_URL,API_KEY,MODEL,TIMEOUT_MS}`. Safe no-op when API_KEY is unset (so this PR is shippable disabled and flipped on later with one env var). Supports JSON-mode. - `runtime/coach/fast-advisor.js` (new) — tactical advisor tier. `advise()` returns `{action: 'switch_skill'|'continue'|'wait', skillId?, rationale}`. **Not auto-triggered in rc.1** — scaffold only, wired into the reflex in rc.3. - `runtime/coach/advice.js` — `normalisePreferSkill()` hardened to reject anything not in registry/mode-map. Warn line when a hallucinated id is dropped. - `runtime/coach/postmortem.js`, `runtime/coach/reflect.js` — embed `skillRegistryPrompt()` in their Pi prompts. Filter Pi's response at write time. ## Configuration (when ready to flip on) ```bash # TimeWeb (or any OpenAI-compatible endpoint) export PEPA_FAST_LLM_BASE_URL="https://<endpoint>/v1" export PEPA_FAST_LLM_API_KEY="<key>" export PEPA_FAST_LLM_MODEL="gpt-5-mini" # or whatever ``` Without the env vars, fast-advisor is a no-op and the bot runs exactly as before — just with hallucinated lessons properly rejected. ## Test plan - [x] `npm test` — 279 green (was 257 on rc.3, +24 new): - 5 tests for `skill-registry` - 9 tests for `llm/provider` (incl. stubbed fetch + 401 path) - 10 tests for `fast-advisor` (incl. rejection of hallucinated skill) - `advice.test.js` regression: `normalisePreferSkill("relocate.surface")` → null - [ ] After merge: deploy and watch `SELECT source, COUNT(*), SUM(applied_count > 0) FROM lessons GROUP BY source` — Pi-coach / Pi-reflect `applied` count should start growing. ## Followup (not in this PR) - rc.2: `runtime/manifesto/needs.js` (L0-L10 Maslow ladder) + curriculum integration. Bot pursues concrete intermediate goals (tools_wood → shelter_basic → tools_stone → ...) instead of "explore further". - rc.3: Event-driven awareness layer + `AbortSignal` plumbing into long-running skills, so the bot reacts to forced moves / HP plunges / hostile spawns within ~100ms instead of waiting for the next dispatch tick. Awareness layer is what auto-triggers `fast-advisor.advise()`. See [`dev/v0.3.0/PLAN.md`](dev/v0.3.0/PLAN.md) for the full design and [`dev/v0.3.0/STATUS.md`](dev/v0.3.0/STATUS.md) for shipped/pending. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
halofourteen commented 2026-05-27 18:09:36 +03:00 (Migrated from github.com)

Superseded by single v0.3.0 PR (rolling rc.1+rc.2+rc.3 into one)

Superseded by single v0.3.0 PR (rolling rc.1+rc.2+rc.3 into one)

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.