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.
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:
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):
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 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
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
## 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)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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:
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 = 0because Pifabricated skill ids (
relocate.surface,choose.safe.surface,survive.shelter,gather.visible_log,tunnel-outwithoutrecovery.prefix). Fixed at write-time AND read-time.runtime/skill-registry.js— single source of truth wrappingskills/index.js. ExportslistSkillIds,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#normalisePreferSkillnow returnsnullfor any id not in registry/mode-map. Warn-logged when dropped.
runtime/llm/provider.js— OpenAI-compatible chat client. Env-drivenvia
TIMEWEB_BASE_URL/TIMEWEB_API_KEY/TIMEWEB_MODEL(samenaming 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. Ratelimited 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.farinstead ofrecognising "I need wood → planks → pickaxe first". Pi could see this
in reflections but had no internal-state language to express "L2 unmet".
runtime/manifesto/needs.js— 11-need catalogue withdetect(snapshot)andpursue(snapshot)per need. Pursue canreturn
null(armour, etc.) and the ladder gracefully skips thatlevel — no stalling on missing skills.
runtime/manifesto/state.js—pickActiveNeed(snapshot)walks theladder, returns
{need, skillId, args, blockedNeeds}. 3s cache.Validates skillId against the live registry — manifesto literally
cannot ship a hallucinated id.
runtime/reflex.js#curriculumReflexconsults manifesto FIRST; thecurriculum plan is the fallback when manifesto has no concrete
pursue. Tests can pass
ctx.disableManifesto = trueto exercisethe curriculum branch in isolation.
runtime/coach/reflect.js— Pi self-reflection prompt now includesthe 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.faragainst the wrong assumptions.runtime/awareness/events.js— directbot.on(…)listeners:move: single-tick Δposition ≥ 5b →forced_move+ preempthealth: HP drop ≥ 2 →health_plunge+ preemptentitySpawn: hostile mob ≤ 12b →hostile_added+ preemptblockUpdate: nearby block change →env_changed(no preempt,throttled 800ms; otherwise gather skills would self-preempt)
runtime/skills/index.js— newRUNNER_CODES.PREEMPTED.runSkill()races
execute()withctx.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—dispatchActioncreates a freshAbortControllerper dispatch.
attachAwareness(bot, {onPreempt})firescontroller.abort()when an env shock hits.Behavioural diff
gather.logs(random)survive.acquire-food(L1)gather.logs(loop)gather.wool(L3)survive.flee(L0)explore.fargather.logsConfiguration (when ready to enable fast advisor)
.env— placeholder added (currently empty, fast advisor is a no-op):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 test— 332 green (was 257 on v0.2.0-rc.3, +75 new):skill-registry.test.jsllm/provider.test.jscoach/fast-advisor.test.jsmanifesto/needs.test.jsmanifesto/state.test.jsawareness/events.test.jsskills/contract.test.jsreflex.test.jsappliedcount to start growing — thefeedback loop was previously broken at the registry boundary.
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)
fast-advisor.advise()from awareness layer whenrecent skills repeat && hasPreempting. Scaffold + tests are inthis PR; just needs the wiring decision.
wedged > 60scraft.iron-*andcraft.leather-*skills (L5, L7, L8 currentlyhave
pursue=nulland rely on lower-level mining/exploration toproduce raw materials)
recentPreemptscount to scenario-memory for pattern learningSupersedes #24, #25, #26 (closed). See
dev/v0.3.0/PLAN.mdfor the full design anddev/v0.3.0/STATUS.mdfor shipped status.🤖 Generated with Claude Code