Files
pepa-pi-bot/dev/v0.3.0/STATUS.md
T
mayatnikovandClaude Opus 4.7 0d97ccccfa feat(v0.3.0): paradigm shift — TimeWeb-only LLM + persistent advisor trail + improvement queue
This is the rc.4 batch the user requested:

  1. Emergency triggers (low HP + close hostile, lava-under-foot)
     bypass the long cooldown so the LLM is consulted BEFORE the bot
     dies, not after.
  2. Active manifesto need is now included in the advisor user prompt
     — the LLM picks suggestions that satisfy the bot's current
     concrete need (L2 tools_wood → "gather logs nearby" not
     "explore further").
  3. Every advisor recommendation is persisted to SQLite
     (advisor_recommendations table) with full token usage. The
     reflex marks 'applied=1' when it dispatches and updates
     outcome_ok/code when the dispatch completes. Ground truth for
     "is the LLM actually helping" lives in the DB, not in logs.
  4. Pi CLI is OUT of every background loop. coach/postmortem and
     coach/reflect now go through the same TimeWeb endpoint
     fast-advisor uses, via the shared coach/llm-call.js helper.
     Pi is reserved for manual operator commands.
  5. The LLM (postmortem, reflect, advisor) can flag "structural
     gaps" — missing skills/features the operator should implement.
     These land in the new improvement_requests table. Dedup by
     title bumps `votes` instead of inserting duplicates so the
     queue doesn't bloat. Operator views via
     `node scripts/list-improvements.js`.
  6. A deterministic trigger-tuner runs hourly: reads 24h of
     recommendation stats, flags triggers whose success rate is
     below 25% (sample ≥ 5) or whose prompts are expensive (>1000
     input tokens) with mediocre payoff. Improvements get
     source="tuner", category="tuning". No LLM call.

New files:
  runtime/coach/llm-call.js        — askAnalytical() helper
  runtime/coach/trigger-tuner.js   — stats → improvements
  runtime/coach/trigger-tuner.test.js
  scripts/list-improvements.js     — operator CLI

Schema additions:
  advisor_recommendations: id, ts, trigger_reason, planned_skill,
    recommended_skill, action, rationale, active_need, tokens_in,
    tokens_out, latency_ms, applied, outcome_ok, outcome_code, outcome_at
  improvement_requests: id, ts, source, category, title, description,
    context, priority, status, duplicate_of, votes, implemented_at, notes

Renamed env-var consumers:
  Pi-coach drainOnce({ askPi })   → drainOnce({ askAnalyticalFn? })
  Pi-reflect runOnce({ askPi })   → runOnce({ askAnalyticalFn? })
  bot.js attachCoach/attachReflect no longer pass askPi
  attachTuner() added to bot.js spawn handler
  lessons.source 'pi-coach'   → 'timeweb-coach'
  lessons.source 'pi-reflect' → 'timeweb-reflect'

Token cost measured live:
  ~705 input + 45 output = ~750 total per advisor call
  worst case @ 6 calls/hour rate cap = ~108K tokens/day
  OpenAI gpt-5-mini reference price: ~$0.60/month

Operator usage:
  node scripts/list-improvements.js                # open queue
  node scripts/list-improvements.js --stats        # advisor performance
  node scripts/list-improvements.js --done 17 "shipped in 0.3.1"
  node scripts/list-improvements.js --reject 18 "duplicate"

Tests: 360 green (was 332, +28 new).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 19:12:48 +03:00

13 KiB

pepa v0.3.0 — status

Live tracking document for the v0.3.0 iteration ("Maslow + Awareness"). See PLAN.md for the full design.

Shipped

rc.1 — Live skill registry + Fast advisor scaffold

Root problem solved: 47/47 Pi-extracted lessons in v0.2.x had applied_count = 0 because Pi was hallucinating skill ids (relocate.surface, choose.safe.surface, survive.shelter, gather.visible_log, …) that don't exist in the registry. Both halves fixed: (a) Pi now sees the real registry in its system prompt, (b) anything that still slips through gets rejected at consult time.

  • runtime/skill-registry.js — single source of truth wrapping skills/index.js. Exports:
    • listSkillIds() — live id list
    • isRegistered(id) — bool check
    • describeSkill(id) — id/title/timeoutMs
    • skillRegistryPrompt({ limit }) — prompt-ready block grouped by namespace, with "USE ONLY THESE, never invent" instruction
  • runtime/llm/provider.js — OpenAI-compatible chat client, env-driven:
    • TIMEWEB_BASE_URL (default https://api.openai.com/v1)
    • TIMEWEB_API_KEY (required to enable; safe no-op otherwise)
    • TIMEWEB_MODEL (required)
    • TIMEWEB_TIMEOUT_MS (default 8000)
    • Supports JSON-mode via response_format: { type: "json_object" }
    • Surfaces not_configured, no_model, http_<status>, network_error, timeout, bad_json codes
  • runtime/coach/fast-advisor.js — tactical "what now?" tier. Scaffold only in rc.1; auto-trigger comes in rc.3.
    • advise({snapshot, reason, recentSkillIds, lessonsTail}){action: 'switch_skill'|'continue'|'wait', skillId?, rationale}
    • Rejects any returned skill_id not in the live registry
    • Rate-limit: 6 calls/hour, 30s cooldown between calls
    • System prompt embeds registry; user prompt carries snapshot + trigger
  • runtime/coach/advice.js:
    • normalisePreferSkill() now returns null for anything not in registry/mode-map (was: passed through unchanged → dispatcher crashed at runSkill())
    • Logs warn line when a hallucinated prefer_skill is dropped
  • runtime/coach/postmortem.js:
    • Pi prompt includes the live registry block (skillRegistryPrompt) with a "CRITICAL: USE ONLY THESE" instruction
    • On insert, drops prefer_skill/avoid_skill that's neither a registered id nor a known mode name; warn-logs the count
  • runtime/coach/reflect.js — same treatment as postmortem (registry in prompt + write-time filter)

Tests: 279 green (was 257 on rc.3). Added:

  • runtime/skill-registry.test.js — 5 tests
  • runtime/llm/provider.test.js — 9 tests
  • runtime/coach/fast-advisor.test.js — 10 tests

rc.2 — Manifesto / Needs ladder L0-L10

Root problem solved: pre-v0.3.0 the bot had no notion of intermediate goals. The curriculum produced a single "next milestone" but no hierarchy. So when the bot was wedged with no pickaxe, it kept trying explore.far instead of recognising "I need wood → planks → pickaxe first". Lessons from Pi couldn't help because there was no internal-state language to express "L2 not satisfied".

The needs ladder gives the bot an explicit, ordered list of survival concerns. Each reflex tick picks the LOWEST unsatisfied need and dispatches a concrete skill toward it.

L0 alive          HP>5, food>0, no lava, no creeper@close
L1 food           ≥6 food items in inventory (or hungry+have any)
L2 tools_wood     wooden_pickaxe + wooden_axe + wooden_sword
L3 shelter_basic  bed placed nearby or in inventory
L4 tools_stone    stone tier (pickaxe + axe + sword)
L5 armor_basic    any chestplate equipped (pursue=null for now)
L6 food_security  ≥16 food items
L7 tools_iron     iron tier (pursue=gather.stone until craft.iron-* lands)
L8 armor_iron     iron chestplate (pursue=null for now)
L9 village_seed   bed + chest nearby
L10 village_full  global goal (never detected, falls through to curriculum)
  • runtime/manifesto/needs.js — catalogue of 11 needs. Each has detect(snapshot) and pursue(snapshot). Pursue can return null (e.g. armor levels) and the ladder gracefully skips, recording the level as "blocked".
  • runtime/manifesto/state.jspickActiveNeed(snapshot) walks the ladder, picks the first unsatisfied + pursuable need. Returns {need, skillId, args, blockedNeeds}. 3-second cache to avoid re-walking the ladder on every micro-tick. Validates skillId against the live registry (rc.1 piece) before returning — manifesto can't ship a hallucinated id.
  • runtime/reflex.js:
    • curriculumReflex now consults manifesto FIRST. If a need dictates a skill, that's what gets dispatched. 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 (L2 tools_wood → gather.logs (Деревянные орудия)) so Pi can give level-appropriate advice instead of generic suggestions.

Tests: 315 green (was 279 on rc.1, +36 new):

  • runtime/manifesto/needs.test.js — 24 tests (one per need detect/pursue)
  • runtime/manifesto/state.test.js — 10 tests (ladder walk, caching, skipping)
  • runtime/reflex.test.js — 2 new integration tests (manifesto-on overrides curriculum; well-fed bot pursues tools_stone)

rc.4 (this commit batch) — Paradigm shift: TimeWeb-only LLM + improvement queue

What changed: Pi (CLI subscription) was removed from every background loop. The bot's analytical LLM path (coach/postmortem, coach/reflect) now goes through the same TimeWeb endpoint the fast advisor already uses. The trigger system was extended with emergency conditions (low HP + close hostile, lava under foot) that bypass the long cooldown. Every recommendation is persisted to SQLite with its outcome, and a deterministic tuner watches the stats to flag underperforming triggers. The LLM also writes a queue of "structural gaps" — missing skills or features — that the operator reviews and implements by hand.

  • runtime/coach/llm-call.js — shared askAnalytical() helper that wraps runtime/llm/provider.js#complete() with a longer (30s) timeout suitable for postmortem and reflect.
  • runtime/coach/postmortem.js:
    • Drain loop runs through TimeWeb, not Pi CLI
    • buildPrompt() returns {system, user} (was a single concatenated string)
    • Reply schema includes improvements[] for missing-skill callouts
    • lessons source is now timeweb-coach (was pi-coach)
  • runtime/coach/reflect.js — same treatment. lessons source is now timeweb-reflect.
  • runtime/coach/advisor-trigger.js:
    • Emergency triggers added: HP≤6 + hostile≤8b, or lava under foot. Use a much shorter 20s cooldown — wait-on-cooldown would be lethal.
    • Active need now passed to the LLM so suggestions track the manifesto.
    • Every recommendation is insertRecommendation()-ed; reflex marks applied=1 when it dispatches, and outcome_ok when the skill returns.
  • runtime/knowledge/schema.sql: two new tables.
    • advisor_recommendations — ground truth for the LLM trail with full token usage + outcome attribution
    • improvement_requests — operator-facing queue. Dedup by title bumps votes instead of inserting duplicates.
  • runtime/coach/trigger-tuner.js (new) — hourly: reads 24h of recommendation stats, flags low-success triggers and expensive-prompt-mediocre-payoff cases as improvement_requests with source="tuner". No LLM call needed — pure SQL.
  • runtime/llm/provider.js: complete() now returns usage: {in, out, total} and logs in=Nt/out=Mt on every call.
  • runtime/coach/fast-advisor.js: getUsageSnapshot() aggregates total tokens across the session; surfaces in scripts/list-improvements.js --stats.
  • scripts/list-improvements.js (new) — operator CLI. --status open (default), --stats, --done <id> [note], --inprogress <id>, --reject <id>, --source <postmortem|reflect|advisor|tuner|manual>, --category <skill|tuning|...>.

Cost measurement (smoke-test against TimeWeb gpt-5.4-mini): per advise(): ~705 input + 45 output = ~750 tokens rate cap: 6 calls/hour worst case @ full hourly cap: ~108K tokens/day estimated cost (OpenAI gpt-5-mini reference pricing): ~$0.60/month

Tests: 360 green (was 332 on v0.3.0-rc.3, +28 new): +3 abortSignal tests in skills/contract.test.js +13 advisor-trigger tests +4 emergency-trigger tests +4 knowledge-recommendation tests +3 knowledge-improvement tests +2 postmortem/reflect rewrites for TimeWeb path +7 trigger-tuner tests (low success / expensive / healthy / dedup)

rc.3 — Event-driven awareness + skill pre-emption

Root problem solved: in v0.2.x the reflex was purely polling. The loop took a snapshot every DISPATCH_INTERVAL_MS (~2s) and decided what to do, but anything that happened between ticks was invisible. Concretely: when the operator dug a path that let the bot fall to a new area, the bot continued executing its prior explore.far against stale assumptions until the next tick. By then it had wandered further off course, and the cycle never broke. Same problem for hostile spawns and HP plunges — the reflex saw them only after the current skill ran its 30-90s timeout.

This rc gives the reflex an event-driven layer that preempts the in-flight skill within ~100ms of an environmental shock.

  • runtime/awareness/events.js — wires direct bot.on(...) listeners and surfaces them as flags + an optional preempt callback:
    • bot.on("move") — single-tick position jump ≥ 5 blocks (teleport, fall, pathfinder snap, operator pushed us) → forced_move
    • bot.on("health") — HP drop ≥ 2 in one tick → health_plunge
    • bot.on("entitySpawn") — hostile mob spawns within 12 blocks → hostile_added
    • bot.on("blockUpdate") — block change within manhattan 4 → env_changed (informational only, NOT preempting; throttled 800ms)
  • runtime/skills/index.js:
    • RUNNER_CODES.PREEMPTED — new stable failure code
    • runSkill() now races execute() with ctx.abortSignal. If the signal fires mid-await, the skill returns { ok: false, code: "preempted" } within one microtask — no skill code change needed. Long-running skills (gather.logs, explore.far, recovery.tunnel-out, survive.pillar-up) get this for free.
  • runtime/bot.js:
    • dispatchAction creates a fresh AbortController per dispatch and stores it on reflexCtx.currentAbort + reflexCtx.abortSignal
    • bot.once("spawn") calls attachAwareness(bot, {onPreempt}) where onPreempt aborts the current dispatch
    • reflexCtx.lastPreempt records the most recent shock for snapshot/telemetry consumers

Tests: 332 green (was 315 on rc.2, +17 new):

  • runtime/awareness/events.test.js — 12 tests (each event type, thresholds, throttling, hostile filter)
  • runtime/skills/contract.test.js — 3 new preempt tests (mid-flight abort, pre-armed signal, clean signal doesn't interfere)
  • 2 extra contract sanity checks shaken out by signal plumbing

Next session quick start

  1. Read PLAN.md for the full design and per-rc breakdown.
  2. Check live DB to see if Pi-lesson application is improving:
    sqlite3 state/play.xmatic.team_25565/knowledge.db \
      "SELECT source, COUNT(*) AS n, SUM(applied_count > 0) AS applied
       FROM lessons GROUP BY source ORDER BY n DESC;"
    
    After rc.1 deploys, expect Pi-coach/Pi-reflect applied count to start growing as the registry feedback closes the loop.
  3. Set fast-advisor env when ready to test:
    export TIMEWEB_BASE_URL="https://<timeweb-endpoint>/v1"
    export TIMEWEB_API_KEY="<key>"
    export TIMEWEB_MODEL="gpt-5-mini"
    
    The advisor still isn't auto-triggered in rc.1 — it's wired in rc.3.
  4. Pick the next rc from PLAN.md.

Workflow notes

  • main is protected — only operator merges PRs
  • Tests: npm test (279 green at last check), isolated under /tmp/
  • The bot supervisor hot-restarts on file changes in runtime/**/*.js
  • If something regresses badly, revert to v0.2.0-rc.3 commit 865aae1