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

Roots out the v0.2.x failure mode: Pi-extracted lessons routinely named
hallucinated skill ids (relocate.surface, choose.safe.surface,
survive.shelter, gather.visible_log, …). All 47 Pi-lessons in the live DB
had applied_count=0 because normalisePreferSkill couldn't find them.

Fix:
1. runtime/skill-registry.js — single source of truth derived from
   skills/index.js. Exports listSkillIds, isRegistered, and a
   prompt-ready block (skillRegistryPrompt) grouped by namespace.
2. Pi prompts (coach/postmortem, coach/reflect) embed the live registry
   with a "USE ONLY THESE, never invent" instruction. Lessons are
   filtered at write-time too — anything not in the registry and not a
   known mode name gets dropped.
3. coach/advice.js — normalisePreferSkill now returns null for unknown
   ids, hardening consult() against any hallucinations that slip
   through. Warn-logged for visibility.

Also lays the LLM substrate for the rest of v0.3.0:

- runtime/llm/provider.js — OpenAI-compatible chat client. Configured
  via PEPA_FAST_LLM_{BASE_URL,API_KEY,MODEL,TIMEOUT_MS}. Safe no-op
  unless API_KEY is set. Supports JSON-mode.
- runtime/coach/fast-advisor.js — tactical advisor tier (scaffold).
  Exposes advise() that asks the fast LLM what to do RIGHT NOW when
  the reflex is wedged/stuck. Rejects hallucinated skill ids using the
  registry. Rate-limited 6/h, 30s cooldown. Not auto-triggered yet —
  wired into reflex in rc.3 (awareness layer).

Tests: 279 green (+24 vs rc.3): 5 registry, 9 provider, 10 advisor.

See dev/v0.3.0/PLAN.md for the full iteration design (manifesto needs
ladder, event-driven awareness, skill pre-emption) and STATUS.md for
shipped/pending tracking.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 17:39:43 +03:00
co-authored by Claude Opus 4.7
parent 865aae1213
commit fcfa2277ba
13 changed files with 1113 additions and 20 deletions
+155
View File
@@ -0,0 +1,155 @@
# pepa v0.3.0 — Maslow + Awareness
Concept: needs-based hierarchical agent with real-time event awareness and a
fast LLM tactical advisor. Pi (CLI) stays for slow deep analytics
(postmortem, reflection). A second, fast LLM tier (TimeWeb / OpenAI-
compatible) handles "what to do right now" decisions when the reflex
detects wedged/stuck/changed environment.
This is a major behavioural rewrite over v0.2.x:
- v0.2.x had a flat reflex chain (modes → defend → eat → sleep →
curriculum → idle) — curriculum was just "next mode". The bot had no
internal concept of "do I have a pickaxe?" let alone "do I have a
shelter?" Lessons from Pi were hallucinated skill names (47/47
Pi-extracted lessons applied_count=0 as of v0.2.0-rc.3).
- v0.3.0 introduces a **needs ladder** (Maslow-like) that drives the
bot's intent end-to-end, an **awareness layer** that reacts to env
changes within ~100ms instead of waiting for the next tick boundary,
and a **fast LLM** tier that closes the loop when the bot is stuck.
## Concept terms
This pattern is called variously in the AI literature:
- **Hierarchical Task Network (HTN)** planning — Voyager uses this
- **Needs-based / utility AI** — game-AI mainstream
- **BDI agent** (Beliefs-Desires-Intentions) — academic AI
- **Subsumption architecture** (Brooks) — reactive layers preempt
deliberative layers when conditions trigger
Pepa v0.3.0 is essentially **Maslow-stack curriculum + Brooks-style
preemption + dual-tier LLM (fast tactical + slow analytical)**.
## Manifesto / Needs ladder
```
L0 alive HP>5, не тонет, не горит, не падает с фатальной высоты
L1 food ≥6 насыщения (готов кушать на месте)
L2 tools_wood wooden_pickaxe + wooden_axe + wooden_sword
L3 shelter_basic 4 стены + крыша + кровать в радиусе 8 от спавн-base
L4 tools_stone stone_pickaxe + stone_axe + stone_sword
L5 armor_basic хотя бы один кусок (predпочтительно нагрудник)
L6 food_security ≥16 еды + источник (ферма / стая коров рядом)
L7 tools_iron iron_pickaxe + iron_axe + iron_sword
L8 armor_iron полный iron set
L9 village_seed 2+ постройки, забор/оградка, базовая ферма
L10 village_full глобальная цель (ферма + дом + сосед-NPC мечта)
```
На каждом тике reflex выбирает **самую нижнюю неудовлетворённую** нужду.
Эта нужда становится **активной**. Curriculum.next() и Pi-coach подсказки
дальше выбираются **внутри** активной нужды. Если нужда сменилась
(например, HP упало → L0 проснулся), текущий skill прерывается.
## v0.3.0 release plan (3 rc)
### rc.1 — Skill registry hardening + Fast advisor scaffold
**Цель**: убрать главную проблему v0.2.x — Pi инвентит skill names.
Не вводим манифест ещё, но строим инфраструктуру для него.
- `runtime/skill-registry.js` — exported `listSkillIds()`, `isRegistered(id)`,
`skillRegistryPrompt()` (готовый блок текста для LLM-промпта со списком
валидных id, по группам)
- Все Pi-промпты (`coach/postmortem.js`, `coach/reflect.js`) передают
реестр в system prompt
- `runtime/coach/advice.js`: `normalisePreferSkill` строго отбрасывает
всё, что не в реестре (raise log, не дрейфит на fuzzy)
- `runtime/llm/provider.js` — OpenAI-совместимый клиент, конфигурируется
через env (`PEPA_FAST_LLM_BASE_URL`, `_API_KEY`, `_MODEL`); graceful
fallback "no-op" если env не задан (бот не падает)
- `runtime/coach/fast-advisor.js` — функция `advise({snapshot, reason})`
с rate-limit (макс. 6 вызовов/час), таймаут 8с, JSON-парсинг ответа
через тот же `extractJson()` что у Pi. **Пока не подключаем к reflex**
scaffold + тесты
- Тесты для каждого нового модуля + регрессионный тест:
`advice.test.js` проверяет что hallucinated `relocate.surface` falls
through (никакой override)
- Минимум 270+ зелёных тестов
### rc.2 — Manifesto / Needs ladder + curriculum integration
**Цель**: bot acts toward concrete needs, not toward "explore further".
- `runtime/manifesto/needs.js` — каталог 11 нужд, каждая со схемой:
```
{ id, level, detect(snapshot) → boolean satisfied, prefer_skill_for_pursuit, ... }
```
- `runtime/manifesto/state.js` — `pickActiveNeed(snapshot)` возвращает
самую нижнюю неудовлетворённую. Кеширует на 5с.
- `runtime/reflex.js`:
- В `curriculumReflex` сначала `activeNeed = pickActiveNeed(...)`
- Skill подбирается в первую очередь по `activeNeed.prefer_skill_for_pursuit`
- Fallback на curriculum.next() только если нужда не дала однозначного skill
- `runtime/coach/advice.js`: `consult()` теперь принимает `activeNeed` и
отбрасывает lessons чьи trigger_situation противоречит текущей нужде
(например, "избегай ночью гулять" не применяется когда L0=alive в опасности)
- Pi-промпты (postmortem, reflect) получают `currentNeed: "L2 tools_wood"`
и просят Pi дать совет именно для этого уровня
- Новый персонаж reflex hook: при смене activeNeed бот произносит в чате
"пора заняться X" (Russian narration tying into chatter.js)
- Тесты: каждая нужда имеет 2-3 теста (detect satisfied/unsatisfied,
правильный prefer_skill)
### rc.3 — Event-driven awareness + skill pre-emption
**Цель**: bot reacts within ~100ms to env changes (fall, teleport,
damage, hostile spawn near).
- `runtime/awareness/events.js` — установка listeners:
- `bot.on('move')` — детект position-jump >5 блоков за тик → событие
`forced-move` → инвалидация current dispatch context
- `bot.on('health')` — снижение HP > 2 за тик → reflex.preempt()
- `bot.on('entitySpawn')` — враждебный <12 блоков → reflex.preempt()
- `bot.on('blockUpdate')` около бота (manhattan <4) → пометка
`environment_changed=true`
- `runtime/awareness/state.js` — храним flags `(forcedMove, lastDamage,
hostileAdded, envChanged)`, expose `consumeFlags()` для reflex
- Skill protocol extended: `execute(ctx, args)` теперь получает
`ctx.abortSignal` (AbortSignal). Длинные операции (pathfinder.goto,
collectBlock loops) проверяют `signal.aborted` между шагами и сразу
возвращают `{ok: false, code: 'preempted'}`
- `runtime/reflex.js`: при срабатывании preempt-флагов вызывается
`currentDispatch?.abort()`, и reflex запускает следующий тик
немедленно (не ждёт `DISPATCH_INTERVAL_MS`)
- `recovery.tunnel-out`, `survive.pillar-up`, `gather.logs`, `explore.far`
адаптируются под AbortSignal (минимальное — `if (signal.aborted)
return { ok:false, code:'preempted' }` после каждого `await`)
- **Связка с fast advisor**: когда preempt сработал из-за `forcedMove`
или environment_changed, и reflex не находит очевидный skill, вызывает
`fastAdvisor.advise(...)` чтобы получить тактический совет (rc.1
scaffolding активируется здесь)
### Acceptance signals (после rc.3)
- В живой БД: `lessons WHERE applied_count > 0` растёт (сейчас 3,
должно стать 20+ за сутки)
- Bot движется к конкретным целям: видимый прогресс инвентаря (wood →
pickaxe → stone → axe), а не "блуждание в одном квадранте"
- При forcedMove бот меняет план в течение секунды, не продолжает
старый skill
- Fast advisor пакетно срабатывает <10 раз/час, каждый раз приводит к
смене skill (логируется)
## Что отложено в v0.3.1
- **Vision** (multimodal LLM на скриншотах) — требует prismarine-viewer
pipeline + multimodal model в провайдере; не в первом релизе
- **Vector memory of scenarios** — embeddings от похожих ситуаций
- **Auto-curriculum from wiki** — фоновый паук minecraft.wiki
## Workflow notes
- Каждый rc — отдельный PR, мержим после approve
- main защищён, auto-patch открывает PR с тегом `auto-patch`
- Если что-то ломается в проде (живой бот в петле >30 мин), откатываем
на v0.2.0-rc.3 commit `865aae1` через `git checkout <commit>` на
ветке `revert/v0.3.0-stability`
- Тестовые данные строго в `/tmp/pepa-test-state-*` (исправлено в v0.2.0-rc.2)