* 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>
* v0.3.0-rc.2: manifesto / needs ladder L0-L10
Adds an explicit hierarchical needs catalogue that the reflex consults
on every tick. The bot now pursues tangible intermediate goals (food,
wood tools, shelter, stone tools, ...) instead of inheriting whatever
the curriculum thought was "next".
Ladder:
L0 alive HP>5, food>0, not in lava, not panic-near hostile
L1 food ≥6 food items in inventory (or sated + any food)
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-*
lands; ladder gracefully skips)
L6 food_security ≥16 food items
L7 tools_iron iron-tier triplet (pursue=gather.stone for now)
L8 armor_iron iron chestplate (pursue=null for now)
L9 village_seed bed + chest in nearby blocks
L10 village_full never detected, falls through to curriculum
Each need has detect(snapshot) → bool and pursue(snapshot) →
{skillId, args} | null. The ladder picks the LOWEST unsatisfied
pursuable need. Needs whose pursue is null get recorded as
blockedNeeds and the walk continues — no stalling on missing skills.
Wired into curriculumReflex: manifesto takes precedence over
curriculum.plan when it has a concrete suggestion. Tests can pass
ctx.disableManifesto=true to exercise the curriculum branch
in isolation (existing reflex tests keep passing this way).
Pi self-reflection prompt now includes
"activeNeed (Maslow ladder L0-L10): L2 tools_wood → gather.logs"
so Pi advises at the right level instead of giving generic guidance.
skillId returned by pursue() is validated against the live registry
(rc.1 plumbing) — manifesto cannot accidentally dispatch a
hallucinated skill name.
Tests: 315 green (was 279 on rc.1, +36 new):
- runtime/manifesto/needs.test.js — 24 tests (per-need detect/pursue,
helper sums)
- runtime/manifesto/state.test.js — 10 tests (ladder walk, hostile
takeover at L0, armor skipping, caching)
- runtime/reflex.test.js — 2 integration tests (manifesto overrides
curriculum plan; well-fed bot pursues tools_stone)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* v0.3.0-rc.3: event-driven awareness + skill pre-emption
Adds a reactive layer on top of the polling reflex. The bot now
notices environmental shocks (forced moves, HP plunges, hostile
spawns) within ~100ms instead of waiting for the next DISPATCH tick,
and the in-flight skill is preempted so the next reflex cycle can
re-plan against the current world state.
This is the rc that wires the "rc.1 plumbing + rc.2 manifesto" into
a feedback loop:
- awareness fires preempt → dispatch aborts
- reflex tick re-evaluates → manifesto walks the ladder
- new dispatch picks the right skill for the new world state
Pieces:
- runtime/awareness/events.js (new) — bot.on listeners:
- move: single-tick Δposition ≥ 5 blocks → forced_move flag + preempt
- health: HP drop ≥ 2 → health_plunge flag + preempt
- entitySpawn: hostile mob within 12 blocks → hostile_added + preempt
- blockUpdate: nearby block change → env_changed flag (no preempt,
throttled 800ms; otherwise gather skills would self-preempt
every dig)
- runtime/skills/index.js — RUNNER_CODES.PREEMPTED + raceWithAbort()
wraps every execute() against ctx.abortSignal. Existing skills get
preemption for free; they don't have to check the signal manually.
- runtime/bot.js:
- dispatchAction creates a fresh AbortController per dispatch and
stores it on reflexCtx.currentAbort
- attachAwareness fires controller.abort() when something disrupts
the active skill; runSkill returns code: "preempted" and the
reflex moves on
- reflexCtx.lastPreempt records the most recent shock
Tests: 332 green (was 315 on rc.2, +17 new):
- runtime/awareness/events.test.js — 12 tests (each event type +
thresholds + throttling + passive-mob filter)
- runtime/skills/contract.test.js — 3 abortSignal tests
(mid-flight, pre-armed, clean signal)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore(.env): add PEPA_FAST_LLM_* placeholders for v0.3.0 fast advisor
Empty values keep the fast-advisor tier disabled (safe no-op). Fill
in BASE_URL + API_KEY + MODEL to enable. TimeWeb-style endpoint
example included.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore(v0.3.0): rename fast-LLM env vars to TIMEWEB_* (match other projects)
Aligns with the user's other repos (proso) which use TIMEWEB_API_GROK /
TIMEWEB_URL_GROK. Single naming convention across projects avoids the
'which env var was it for this repo' mental tax.
PEPA_FAST_LLM_BASE_URL → TIMEWEB_BASE_URL
PEPA_FAST_LLM_API_KEY → TIMEWEB_API_KEY
PEPA_FAST_LLM_MODEL → TIMEWEB_MODEL
PEPA_FAST_LLM_TIMEOUT_MS → TIMEWEB_TIMEOUT_MS
Provider still works with any OpenAI-compatible endpoint — TimeWeb is
the default but the variable name doesn't lock us in. Tests + docs +
.env / .env.example updated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(scripts): TimeWeb smoke test + bump default LLM timeout to 20s
scripts/check-timeweb.js — three probes: plain text, JSON mode, full
fast-advisor stack (registry injection + skill validation). Loads .env,
prints {ok, latency, reply preview} for each. Doesn't touch bot state.
Bumped DEFAULT_TIMEOUT_MS 8s → 20s in runtime/llm/provider.js. TimeWeb's
hosted agent endpoint takes 5-15s for the fast-advisor prompt
(registry block + snapshot context), so 8s was producing spurious
timeouts. OpenAI direct returns much faster; env var TIMEWEB_TIMEOUT_MS
overrides if needed.
Smoke verified live (PR #27 branch):
probe 1: 6.3s, plain prompt → "pepa hears you"
probe 2: 5.4s, JSON mode → {"alive":true,"name":"pepa"}
probe 3: 14.9s, advise() → action=switch_skill, skill=recovery.tunnel-out
(correct registered skill, sensible rationale — registry
injection successfully prevents hallucination)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(v0.3.0): auto-trigger fast-advisor + token usage tracking
Closes the awareness → LLM → action loop that the rc.1/2/3 sequence
left as a followup. When the bot is wedged, looping, or just suffered
a preempt-then-retry, the reflex fires advise() in the background;
when the recommendation lands it overrides the next dispatch.
Async by design: advise() takes 5-15s on TimeWeb's hosted endpoint —
too slow for a synchronous reflex tick. tickAdvisor() is fire-and-
forget, the result lands on ctx.advisorRecommendation, and the *next*
tick reads and consumes it. Recommendations age out after 60s.
Components:
- runtime/coach/advisor-trigger.js — policy + async fire path
- tickAdvisor(ctx, {plannedSkillId}) checks three triggers:
1. wedged > 60s (no significant move)
2. last 4+ dispatches are the same skill AND it's planned again
3. preempt within last 30s + same skill being retried
- 90s trigger cooldown, single-in-flight guard
- consumeFreshRecommendation(ctx) reads/clears the cache
- runtime/reflex.js — curriculumReflex calls tickAdvisor() every tick
and consumes a fresh recommendation BEFORE dispatching. ctx flag
disableAdvisor=true for tests.
- runtime/bot.js — dispatchAction maintains a rolling 8-slot
reflexCtx.recentSkillIds for the loop-detection trigger.
Token usage:
- runtime/llm/provider.js — normaliseUsage() reads OpenAI/TimeWeb-
style {prompt_tokens, completion_tokens, total_tokens} from the
response. Returned on every complete() result and logged at info
level as "in=Nt/out=Mt".
- runtime/coach/fast-advisor.js — getUsageSnapshot() aggregates
total tokens across all calls in the session.
Measured on live TimeWeb endpoint (gpt-5.4-mini agent):
per call: ~705 input + 45 output = ~750 tokens
rate limit: 6 calls/hour
worst case at full budget: ~108K tokens/day
estimated cost (OpenAI gpt-5-mini reference price): ~$0.60/month
Well within any reasonable budget — model can run hot 24/7.
Smoke verified: scripts/check-timeweb.js probe 4 produces
trigger fired: true (wedged_90s)
recommendation: recovery.tunnel-out
rationale: "Stuck wedged for 90s; exploration is failing."
latency: 5302ms
Tests: 345 green (was 332, +13 advisor-trigger).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* 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>
---------
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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 wrappingskills/index.js. Exports:listSkillIds()— live id listisRegistered(id)— bool checkdescribeSkill(id)— id/title/timeoutMsskillRegistryPrompt({ 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(defaulthttps://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_jsoncodes
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_idnot 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 returnsnullfor anything not in registry/mode-map (was: passed through unchanged → dispatcher crashed atrunSkill())- Logs
warnline 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_skillthat's neither a registered id nor a known mode name; warn-logs the count
- Pi prompt includes the live registry block (
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 testsruntime/llm/provider.test.js— 9 testsruntime/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 hasdetect(snapshot)andpursue(snapshot). Pursue can returnnull(e.g. armor levels) and the ladder gracefully skips, recording the level as "blocked".runtime/manifesto/state.js—pickActiveNeed(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. ValidatesskillIdagainst the live registry (rc.1 piece) before returning — manifesto can't ship a hallucinated id.runtime/reflex.js:curriculumReflexnow 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 = trueto 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— sharedaskAnalytical()helper that wrapsruntime/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 lessonssource is nowtimeweb-coach(waspi-coach)
runtime/coach/reflect.js— same treatment.lessonssource is nowtimeweb-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 marksapplied=1when it dispatches, andoutcome_okwhen the skill returns.
runtime/knowledge/schema.sql: two new tables.advisor_recommendations— ground truth for the LLM trail with full token usage + outcome attributionimprovement_requests— operator-facing queue. Dedup by title bumpsvotesinstead 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 asimprovement_requestswithsource="tuner". No LLM call needed — pure SQL.runtime/llm/provider.js:complete()now returnsusage: {in, out, total}and logsin=Nt/out=Mton every call.runtime/coach/fast-advisor.js:getUsageSnapshot()aggregates total tokens across the session; surfaces inscripts/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 directbot.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_movebot.on("health")— HP drop ≥ 2 in one tick →health_plungebot.on("entitySpawn")— hostile mob spawns within 12 blocks →hostile_addedbot.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 coderunSkill()now racesexecute()withctx.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:dispatchActioncreates a freshAbortControllerper dispatch and stores it onreflexCtx.currentAbort+reflexCtx.abortSignalbot.once("spawn")callsattachAwareness(bot, {onPreempt})whereonPreemptaborts the current dispatchreflexCtx.lastPreemptrecords 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
- Read PLAN.md for the full design and per-rc breakdown.
- Check live DB to see if Pi-lesson application is improving:
After rc.1 deploys, expect Pi-coach/Pi-reflect
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;"appliedcount to start growing as the registry feedback closes the loop. - Set fast-advisor env when ready to test:
The advisor still isn't auto-triggered in rc.1 — it's wired in rc.3.
export TIMEWEB_BASE_URL="https://<timeweb-endpoint>/v1" export TIMEWEB_API_KEY="<key>" export TIMEWEB_MODEL="gpt-5-mini" - 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