feat(runtime/coach,persona): death post-mortem coach + Russian chat narration

Coach (runtime/coach/postmortem.js):
- bot.on('death') → captures context (last skill, hostile, recent
  scenarios, journal nearby, snapshot) → inserts row into knowledge.deaths
- Periodic drain (every 5min, ≤3 Pi calls/hour, 12min cooldown):
  batches up to 8 unanalysed deaths, asks Pi to extract 1-3 generalised
  lessons in JSON, persists to knowledge.lessons + knowledge.postmortems
- Pi prompt asks for structured advice (trigger_skill, trigger_hostile,
  avoid_skill, prefer_skill, confidence) so future dispatch can act on it

Persona (runtime/persona/chatter.js):
- Polls snapshot every 5s, narrates Russian lines on transitions:
  skill start, threat spotted, dusk/dawn, respawn, stuck, milestone done
- Rate-limited: min 75s gap, max 8/hour, duplicate suppression
- ~14 template buckets covering gather/craft/build/travel/combat/weather

Wire-up in bot.js (5 lines):
- initKnowledge({stateDir}) fire-and-forget at module load
- attachCoach + attachChatter inside bot.once("spawn")

Tests: 14 new (7 coach, 7 persona). Total suite 230 green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 13:19:56 +03:00
co-authored by Claude Opus 4.7
parent 3da10ce58c
commit 5449ad2e0d
5 changed files with 902 additions and 0 deletions
+13
View File
@@ -57,10 +57,18 @@ import { createSkillMetrics } from "./skill-metrics.js";
import { createWorldJournal } from "./world-journal.js";
import { createScenarioMemory, situationHash } from "./scenario-memory.js";
import { createOwnedBlocksLedger } from "./owned-blocks.js";
import { initKnowledge } from "./knowledge/index.js";
import { attach as attachCoach } from "./coach/postmortem.js";
import { attach as attachChatter } from "./persona/chatter.js";
fs.mkdirSync(stateDir, { recursive: true });
const JOINED_FLAG = path.join(stateDir, "joined-before.flag");
// Knowledge subsystem boots in the background. If better-sqlite3 isn't
// installed the call returns false and every knowledge API becomes a
// safe no-op. See docs/v0.2.0-self-learning.md.
initKnowledge({ stateDir }).catch((e) => warn("knowledge", `init failed: ${e?.message ?? e}`));
// Auto-escalation tunables. With tick=3s, 20 noops ≈ 1 minute idle before we
// even consider asking Pi. Cooldown prevents spamming the LLM when the bot
// is permanently stuck on the same situation.
@@ -658,6 +666,11 @@ function connect() {
pathWatchdog = createPathfinderWatchdog(bot);
info("pathfinder", "stuck-replan watchdog armed");
} catch (e) { warn("pathfinder", `watchdog start failed: ${e?.message ?? e}`); }
// v0.2.0 — self-learning coach + persona narration. Both are
// import-safe; they just attach listeners and (for coach) a periodic
// Pi-drain timer. See docs/v0.2.0-self-learning.md.
try { attachCoach(bot, { stateDir, askPi }); } catch (e) { warn("coach", `attach: ${e?.message ?? e}`); }
try { attachChatter(bot, { getSnapshot: () => lastSnapshot }); } catch (e) { warn("persona", `attach: ${e?.message ?? e}`); }
});
bot.on("messagestr", (text) => {