v0.3.0: Maslow + Awareness — self-learning bot with needs ladder, event-driven reflex, and TimeWeb fast advisor #27

Merged
halofourteen merged 8 commits from v0.3.0 into main 2026-05-27 19:37:57 +03:00
halofourteen commented 2026-05-27 18:10:48 +03:00 (Migrated from github.com)

What and why

Major iteration over v0.2.x. Concept: needs-based hierarchical agent
with real-time event awareness and a fast LLM tactical advisor.

In v0.2.x the bot accumulated 47 Pi-extracted lessons in the knowledge
DB — and applied 0 of them. It also had no internal concept of
intermediate goals (just "explore further"), no reaction to mid-skill
environment changes (the bot kept executing stale plans for 30-90s
after a forced teleport / HP plunge / hostile spawn), and Pi (CLI)
was the only LLM path — too slow for tactical decisions.

v0.3.0 fixes all three:

awareness fires preempt → dispatch aborts
reflex tick re-evaluates → manifesto walks needs ladder
new dispatch picks the right skill for the current world state

In the AI literature this is utility AI + HTN planning + subsumption
architecture
rolled together. In pepa terms: an explicit Maslow-style
needs ladder, an event-driven reactive layer that preempts the
deliberative loop, and a dual-tier LLM (slow Pi for analysis, fast
TimeWeb for tactics).

Three layers, three commits worth of work

Layer 1: Live skill registry + Fast advisor scaffold

Root problem: 47/47 Pi-lessons had applied_count = 0 because Pi
fabricated skill ids (relocate.surface, choose.safe.surface,
survive.shelter, gather.visible_log, tunnel-out without
recovery. prefix). Fixed at write-time AND read-time.

  • runtime/skill-registry.js — single source of truth wrapping
    skills/index.js. Exports listSkillIds, isRegistered,
    skillRegistryPrompt. Drop-in for any LLM system prompt.
  • runtime/coach/postmortem.js, runtime/coach/reflect.js
    Pi prompts embed live registry with "USE ONLY THESE, never invent"
    instruction. Lessons also filtered at write time.
  • runtime/coach/advice.js#normalisePreferSkill now returns null
    for any id not in registry/mode-map. Warn-logged when dropped.
  • runtime/llm/provider.js — OpenAI-compatible chat client. Env-driven
    via TIMEWEB_BASE_URL / TIMEWEB_API_KEY / TIMEWEB_MODEL (same
    naming convention as the user's other projects — works with any
    OpenAI-compatible endpoint, not just TimeWeb).
  • runtime/coach/fast-advisor.js — tactical "what now?" tier. Rate
    limited 6/h, 30s cooldown. Auto-trigger from awareness deferred
    to v0.3.1
    — scaffold + tests only here; programmatic access works.

Layer 2: Manifesto / Needs ladder L0-L10

Root problem: bot had no concept of intermediate goals. When wedged
without a pickaxe, it kept dispatching explore.far instead of
recognising "I need wood → planks → pickaxe first". Pi could see this
in reflections but had no internal-state language to express "L2 unmet".

L0  alive          HP>5, food>0, not in lava, not panic-near hostile
L1  food           ≥6 food items in inventory
L2  tools_wood     wooden_pickaxe + wooden_axe + wooden_sword
L3  shelter_basic  bed placed nearby or in inventory
L4  tools_stone    stone-tier triplet
L5  armor_basic    any chestplate (pursue=null until craft.leather-*)
L6  food_security  ≥16 food items
L7  tools_iron     iron-tier triplet
L8  armor_iron     iron chestplate
L9  village_seed   bed + chest in nearby blocks
L10 village_full   global goal (always falls through to curriculum)
  • runtime/manifesto/needs.js — 11-need catalogue with
    detect(snapshot) and pursue(snapshot) per need. Pursue can
    return null (armour, etc.) and the ladder gracefully skips that
    level — no stalling on missing skills.
  • runtime/manifesto/state.jspickActiveNeed(snapshot) walks the
    ladder, returns {need, skillId, args, blockedNeeds}. 3s cache.
    Validates skillId against the live registry — manifesto literally
    cannot ship a hallucinated id.
  • runtime/reflex.js#curriculumReflex consults manifesto FIRST; the
    curriculum plan is the fallback when manifesto has no concrete
    pursue. Tests can pass ctx.disableManifesto = true to exercise
    the curriculum branch in isolation.
  • runtime/coach/reflect.js — Pi self-reflection prompt now includes
    the active need so advice lands at the right level.

Layer 3: Event-driven awareness + skill pre-emption

Root problem: reflex was polling-only. Anything between ticks
(forced teleport, HP plunge, hostile spawn) was invisible until the
current skill finished, 30-90s later. The classic failure: operator
digs a path that drops the bot into a new area, bot keeps executing
its stale explore.far against the wrong assumptions.

  • runtime/awareness/events.js — direct bot.on(…) listeners:
    • move: single-tick Δposition ≥ 5b → forced_move + preempt
    • health: HP drop ≥ 2 → health_plunge + preempt
    • entitySpawn: hostile mob ≤ 12b → hostile_added + preempt
    • blockUpdate: nearby block change → env_changed (no preempt,
      throttled 800ms; otherwise gather skills would self-preempt)
  • runtime/skills/index.js — new RUNNER_CODES.PREEMPTED. runSkill()
    races execute() with ctx.abortSignal. If signal fires mid-await,
    skill returns { ok: false, code: "preempted" } within one microtask.
    No existing skill code change needed — they get preemption free.
  • runtime/bot.jsdispatchAction creates a fresh AbortController
    per dispatch. attachAwareness(bot, {onPreempt}) fires
    controller.abort() when an env shock hits.

Behavioural diff

Situation v0.2.x v0.3.0
Fresh spawn, food=20, empty inventory gather.logs (random) survive.acquire-food (L1)
Full wood tools, no bed gather.logs (loop) gather.wool (L3)
Wood + stone tools, no chestplate curriculum plan armor skipped → L6 food security
HP=4, creeper@3m, has bread curriculum plan survive.flee (L0)
Operator digs path, bot falls 8b continues stale explore.far Aborted → next tick replans
Creeper spawns 6m during gather.logs Continues chopping for ~30s Aborted → defendReflex / L0
Skeleton arrow drops HP=4 mid-skill Continues; next tick sees it Aborted → manifesto L0 → flee

Configuration (when ready to enable fast advisor)

.env — placeholder added (currently empty, fast advisor is a no-op):

TIMEWEB_BASE_URL=https://<your-timeweb-endpoint>/v1
TIMEWEB_API_KEY=<paste your key>
TIMEWEB_MODEL=gpt-5-mini   # or whatever TimeWeb exposes

Without these vars the bot runs exactly as v0.2.0-rc.3 plus the
registry + manifesto + awareness fixes (which require no LLM access).

Test plan

  • npm test332 green (was 257 on v0.2.0-rc.3, +75 new):
    • 5 in skill-registry.test.js
    • 9 in llm/provider.test.js
    • 10 in coach/fast-advisor.test.js
    • 24 in manifesto/needs.test.js
    • 10 in manifesto/state.test.js
    • 12 in awareness/events.test.js
    • 3 abortSignal tests in skills/contract.test.js
    • 2 manifesto-integration tests in reflex.test.js
  • Post-merge: deploy and watch
    sqlite3 state/.../knowledge.db \
      "SELECT source, COUNT(*) n, SUM(applied_count>0) applied
       FROM lessons GROUP BY source;"
    
    Expect Pi-coach / Pi-reflect applied count to start growing — the
    feedback loop was previously broken at the registry boundary.
  • Post-merge: watch logs for preempt: aborting <skill> due to <reason>.
    Frequent preempts are a sign awareness is working — should correlate
    with faster recovery from wedged states.

Followup (v0.3.1)

  • Auto-trigger fast-advisor.advise() from awareness layer when
    recent skills repeat && hasPreempting. Scaffold + tests are in
    this PR; just needs the wiring decision.
  • Vision (multimodal LLM on prismarine-viewer screenshots) when
    wedged > 60s
  • craft.iron-* and craft.leather-* skills (L5, L7, L8 currently
    have pursue=null and rely on lower-level mining/exploration to
    produce raw materials)
  • Persist recentPreempts count to scenario-memory for pattern learning

Supersedes #24, #25, #26 (closed). See
dev/v0.3.0/PLAN.md for the full design and
dev/v0.3.0/STATUS.md for shipped status.

🤖 Generated with Claude Code

## What and why Major iteration over v0.2.x. Concept: **needs-based hierarchical agent with real-time event awareness and a fast LLM tactical advisor.** In v0.2.x the bot accumulated 47 Pi-extracted lessons in the knowledge DB — and applied **0 of them**. It also had no internal concept of intermediate goals (just "explore further"), no reaction to mid-skill environment changes (the bot kept executing stale plans for 30-90s after a forced teleport / HP plunge / hostile spawn), and Pi (CLI) was the only LLM path — too slow for tactical decisions. v0.3.0 fixes all three: ``` awareness fires preempt → dispatch aborts reflex tick re-evaluates → manifesto walks needs ladder new dispatch picks the right skill for the current world state ``` In the AI literature this is **utility AI + HTN planning + subsumption architecture** rolled together. In pepa terms: an explicit Maslow-style needs ladder, an event-driven reactive layer that preempts the deliberative loop, and a dual-tier LLM (slow Pi for analysis, fast TimeWeb for tactics). ## Three layers, three commits worth of work ### Layer 1: Live skill registry + Fast advisor scaffold **Root problem**: 47/47 Pi-lessons had `applied_count = 0` because Pi fabricated skill ids (`relocate.surface`, `choose.safe.surface`, `survive.shelter`, `gather.visible_log`, `tunnel-out` without `recovery.` prefix). Fixed at write-time AND read-time. - `runtime/skill-registry.js` — single source of truth wrapping `skills/index.js`. Exports `listSkillIds`, `isRegistered`, `skillRegistryPrompt`. Drop-in for any LLM system prompt. - `runtime/coach/postmortem.js`, `runtime/coach/reflect.js` — Pi prompts embed live registry with "USE ONLY THESE, never invent" instruction. Lessons also filtered at write time. - `runtime/coach/advice.js#normalisePreferSkill` now returns `null` for any id not in registry/mode-map. Warn-logged when dropped. - `runtime/llm/provider.js` — OpenAI-compatible chat client. Env-driven via `TIMEWEB_BASE_URL` / `TIMEWEB_API_KEY` / `TIMEWEB_MODEL` (same naming convention as the user's other projects — works with any OpenAI-compatible endpoint, not just TimeWeb). - `runtime/coach/fast-advisor.js` — tactical "what now?" tier. Rate limited 6/h, 30s cooldown. **Auto-trigger from awareness deferred to v0.3.1** — scaffold + tests only here; programmatic access works. ### Layer 2: Manifesto / Needs ladder L0-L10 **Root problem**: bot had no concept of intermediate goals. When wedged without a pickaxe, it kept dispatching `explore.far` instead of recognising "I need wood → planks → pickaxe first". Pi could see this in reflections but had no internal-state language to express "L2 unmet". ``` L0 alive HP>5, food>0, not in lava, not panic-near hostile L1 food ≥6 food items in inventory L2 tools_wood wooden_pickaxe + wooden_axe + wooden_sword L3 shelter_basic bed placed nearby or in inventory L4 tools_stone stone-tier triplet L5 armor_basic any chestplate (pursue=null until craft.leather-*) L6 food_security ≥16 food items L7 tools_iron iron-tier triplet L8 armor_iron iron chestplate L9 village_seed bed + chest in nearby blocks L10 village_full global goal (always falls through to curriculum) ``` - `runtime/manifesto/needs.js` — 11-need catalogue with `detect(snapshot)` and `pursue(snapshot)` per need. Pursue can return `null` (armour, etc.) and the ladder gracefully skips that level — no stalling on missing skills. - `runtime/manifesto/state.js` — `pickActiveNeed(snapshot)` walks the ladder, returns `{need, skillId, args, blockedNeeds}`. 3s cache. Validates skillId against the live registry — manifesto literally cannot ship a hallucinated id. - `runtime/reflex.js#curriculumReflex` consults manifesto FIRST; the curriculum plan is the fallback when manifesto has no concrete pursue. Tests can pass `ctx.disableManifesto = true` to exercise the curriculum branch in isolation. - `runtime/coach/reflect.js` — Pi self-reflection prompt now includes the active need so advice lands at the right level. ### Layer 3: Event-driven awareness + skill pre-emption **Root problem**: reflex was polling-only. Anything between ticks (forced teleport, HP plunge, hostile spawn) was invisible until the current skill finished, 30-90s later. The classic failure: operator digs a path that drops the bot into a new area, bot keeps executing its stale `explore.far` against the wrong assumptions. - `runtime/awareness/events.js` — direct `bot.on(…)` listeners: - `move`: single-tick Δposition ≥ 5b → `forced_move` + preempt - `health`: HP drop ≥ 2 → `health_plunge` + preempt - `entitySpawn`: hostile mob ≤ 12b → `hostile_added` + preempt - `blockUpdate`: nearby block change → `env_changed` (no preempt, throttled 800ms; otherwise gather skills would self-preempt) - `runtime/skills/index.js` — new `RUNNER_CODES.PREEMPTED`. `runSkill()` races `execute()` with `ctx.abortSignal`. If signal fires mid-await, skill returns `{ ok: false, code: "preempted" }` within one microtask. **No existing skill code change needed** — they get preemption free. - `runtime/bot.js` — `dispatchAction` creates a fresh `AbortController` per dispatch. `attachAwareness(bot, {onPreempt})` fires `controller.abort()` when an env shock hits. ## Behavioural diff | Situation | v0.2.x | v0.3.0 | |------------------------------------------|---------------------------------|-------------------------------------| | Fresh spawn, food=20, empty inventory | `gather.logs` (random) | `survive.acquire-food` (L1) | | Full wood tools, no bed | `gather.logs` (loop) | `gather.wool` (L3) | | Wood + stone tools, no chestplate | curriculum plan | armor skipped → L6 food security | | HP=4, creeper@3m, has bread | curriculum plan | `survive.flee` (L0) | | Operator digs path, bot falls 8b | continues stale `explore.far` | Aborted → next tick replans | | Creeper spawns 6m during `gather.logs` | Continues chopping for ~30s | Aborted → defendReflex / L0 | | Skeleton arrow drops HP=4 mid-skill | Continues; next tick sees it | Aborted → manifesto L0 → flee | ## Configuration (when ready to enable fast advisor) `.env` — placeholder added (currently empty, fast advisor is a no-op): ```bash TIMEWEB_BASE_URL=https://<your-timeweb-endpoint>/v1 TIMEWEB_API_KEY=<paste your key> TIMEWEB_MODEL=gpt-5-mini # or whatever TimeWeb exposes ``` Without these vars the bot runs exactly as v0.2.0-rc.3 plus the registry + manifesto + awareness fixes (which require no LLM access). ## Test plan - [x] `npm test` — **332 green** (was 257 on v0.2.0-rc.3, +75 new): - 5 in `skill-registry.test.js` - 9 in `llm/provider.test.js` - 10 in `coach/fast-advisor.test.js` - 24 in `manifesto/needs.test.js` - 10 in `manifesto/state.test.js` - 12 in `awareness/events.test.js` - 3 abortSignal tests in `skills/contract.test.js` - 2 manifesto-integration tests in `reflex.test.js` - [ ] Post-merge: deploy and watch ```bash sqlite3 state/.../knowledge.db \ "SELECT source, COUNT(*) n, SUM(applied_count>0) applied FROM lessons GROUP BY source;" ``` Expect Pi-coach / Pi-reflect `applied` count to start growing — the feedback loop was previously broken at the registry boundary. - [ ] Post-merge: watch logs for `preempt: aborting <skill> due to <reason>`. Frequent preempts are a sign awareness is working — should correlate with faster recovery from wedged states. ## Followup (v0.3.1) - Auto-trigger `fast-advisor.advise()` from awareness layer when `recent skills repeat && hasPreempting`. Scaffold + tests are in this PR; just needs the wiring decision. - Vision (multimodal LLM on prismarine-viewer screenshots) when `wedged > 60s` - `craft.iron-*` and `craft.leather-*` skills (L5, L7, L8 currently have `pursue=null` and rely on lower-level mining/exploration to produce raw materials) - Persist `recentPreempts` count to scenario-memory for pattern learning Supersedes #24, #25, #26 (closed). 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 status. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.