User report 2026-05-25: launched 'npm run bot' fresh, MC server kicked
every login with "Игрок с данным никнеймом уже играет на сервере" and
the bot fell into a perpetual reconnect-then-kicked loop. Root cause:
a smoke-test supervisor from an earlier shell was still running in the
background, holding the pepa_bot session open. Two supervisors racing
on the same nickname is undefined behaviour from the server's side and
results in this exact failure mode.
Changes:
runtime/supervisor.js — acquires state/<host>/supervisor.pid before
spawning the child. If another supervisor is alive (kill -0 check), the
new one exits with a clear message telling the operator how to recover.
On SIGINT/SIGTERM/exit the lock is released; stale pidfiles are detected
when the recorded PID is no longer alive.
scripts/stop.sh — emergency cleanup helper:
- kills any supervisor or bot.js processes matching this repo
- removes pidfile + bot.sock
- reminds the operator to wait ~30s for the MC server to drop the old
session before re-launching
package.json — new `npm run stop` script.
Smoke-tested:
- first 'node runtime/supervisor.js' acquires lock, writes pid
- second call refuses with diagnostic message
- first SIGTERM ⇒ pidfile removed automatically
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the "bot stands on a tree doing nothing" problem reported live
when the operator launched the TUI after PR #6 landed. The reactive
chain (operator > defend > eat > sleep > idle) was passive by design:
day-time, full HP and food, no hostile within 4 m ⇒ every reflex
returned noop. The bot perched in dark-oak canopy and never moved.
Changes
runtime/actions.js:
- chopNearestTree: find any *_log within 32 blocks, equip best axe
(falls back to fists), path to the block, dig. Per-bot 5-min
blacklist of unreachable log positions so we don't grind on the
same impossible target.
- wander: pick a random offset 6-16 blocks away and path there.
- setMovementsForGather / setMovementsForTravel: every action that
uses pathfinder now sets its own Movements profile (canDig=true)
instead of inheriting whatever the previous caller left. The old
behaviour caused chop to inherit flee's canDig=false and get stuck
in the canopy.
- fleeFrom now uses canDig=true too — the user observed the bot
permanently stuck on a leaf block because escape required digging.
runtime/reflex.js:
- new autonomousReflex between sleep and idle. Cooldown 10s. Picks
chop when log count < 16, else wander. When chop reports "no
reachable log within 32 blocks" we switch to wander for 60s so we
don't re-fire chop against the same impossible position.
- defendReflex tightened: only flee when closest is ≤8m (or ≤12m
on low HP). Avoids the "82 distant hostiles ⇒ constant flee
loop" pathology observed at this spawn.
- flee cooldown: same mob name within 60s ⇒ noop, so we yield to
other reflexes if flee keeps timing out.
- sleepReflex retry cooldown raised 30s → 5min. Sleeping fails
permanently if no bed is around; the short retry blocked
autonomous behaviour every tick.
- ctx.lastReflex now records {name, label, ts} after each
dispatched/completed reflex so the TUI can show what the bot
just decided.
runtime/bot.js:
- per-tick snapshot adds lastReflex and busy fields for the TUI.
- maybeReplyToPlayer: light canned greetings (yo/hey/hi/привет)
to non-operators when they address the bot. 30s cooldown so we
don't spam.
tui/tui.tsx:
- status bar shows either "▸ busy: <label>" while an action is
in flight, or "last reflex: <name> (<label>) Ns ago" when idle.
Gives an at-a-glance answer to "what is the bot doing right now?"
Smoke-tested live (play.xmatic.team, 2026-05-25T13:30-13:39):
- bot did dispatch chop tree (oak_log at 617,82,95)
- real bug surfaced and 3-fail rule filed a proposal automatically
- after the runtime fix, chop returns "no reachable log" gracefully
- bot switched to wander on the next tick
- position moved from (623.31, 85, 95.12) to (623.41, 86.02, 96.7)
— the first observable movement in this dark-oak spawn
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updates docs/runtime.md and README.md to match what's actually shipped:
- reflex chain priorities and what each body now dispatches
- operator chat command list (status, come, pause, resume, stop)
- automatic + manual Pi escalation paths and the no-code-change rule
- the full self-improvement loop end-to-end (detector → TUI approval
→ propose:apply → supervisor restart) with the rationale for the
manual propose:apply step
- new state files layout (proposals/, proposals/approved/, etc.)
- supervisor.js + bot:bare script flags
No code changes.
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the self-improvement loop end-to-end:
reflex fails 3× → proposal file → operator approves in TUI →
`npm run propose:apply <file>` spawns Pi on a feature branch →
Pi commits the patch → supervisor watches runtime/*.js and
restarts the child on change.
runtime/state-store.js — atomic current-task.json writes, daily diary
append, proposals/ + proposals/approved/ helpers.
runtime/bot.js:
- ctx.dispatch writes current-task.json on start and updates it on
completion / failure / throw.
- failure tracker: 3 consecutive same-label failures → writeProposal()
with the snapshot, labels, and a suggested-next-step section.
30-min cooldown prevents proposal spam.
- on startup, surfaces resume info (previous task + pending proposal
count); on death, clears current-task.json + writes diary line.
- new IPC commands: PROPOSAL_LATEST returns the newest pending
proposal body; PROPOSAL_APPROVE moves it to proposals/approved/.
tui/tui.tsx — status bar shows `[proposals N, press y]` badge when
bot.pendingProposals > 0. Hotkey 'y' opens the proposal panel; 'y'
approves, 'n'/Esc closes.
scripts/propose-apply.js — given an approved proposal filename, creates
a `feat/proposal-<slug>` branch and spawns `pi -p` with the proposal
+ repo-conventions prompt. Refuses on dirty tree. No auto-push, no
auto-merge — operator reviews the diff and decides.
runtime/supervisor.js — forks bot.js as a child, watches runtime/*.js,
restarts on file change or on child exit code 42. Rate-limited at 5
restarts/minute. SIGINT/SIGTERM forward cleanly. `npm run bot` now
goes through the supervisor; `npm run bot:bare` skips it.
Smoke-tested: supervisor spawned, bot connected to MC, spawned at
expected coords, diary line written, state cleanup on SIGTERM correct.
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
runtime/actions.js — Mineflayer wrappers with hard timeouts and structured
{ok, detail} returns:
- attackNearest: equip best melee, lookAt, single swing per call
- fleeFrom: lazy-load pathfinder, walk N blocks away (canDig=false to
avoid burrowing through walls under panic)
- eatBestFood: scan inventory by FOOD_PRIORITY, equip + consume
- sleepInBed: find nearest placed bed within 16 blocks, path to it, sleep
- goTo: pathfinder.goto for operator come/follow
runtime/reflex.js — bodies now dispatch real actions via ctx.dispatch:
- operator-goal (highest): satisfy come/follow command
- defend: ≤4m attack, ≤12m + low HP/many hostiles flee
- eat: food < 16 + 5s cooldown
- sleep: night + safe + 30s retry cooldown
- idle: heartbeat every 20th tick
Reflex returns "skipped" when ctx.busy so we don't count busy ticks as
either productive or noop in the escalation counter.
runtime/bot.js:
- ctx.dispatch fire-and-forget wrapper with busy gate, onComplete hook
- consecutiveNoops counter; after ESCALATE_AFTER_NOOPS (=20, ~1 min at
tick=3s), askPi with the current snapshot. 10-min cooldown.
- operator chat handler: parses `<botname> <verb>` messages from
OPERATOR_USERNAMES. Verbs: status, pause, resume, stop, come.
- Death drops any pending operator goal.
Smoke-tested live against play.xmatic.team:25565: bot connected, logged
in via AuthMe, reflex chain dispatched flee/sleep, hard timeout fired
when pathfinder couldn't reach the flee target (expected — no usable
ground path in dark_forest at this spawn).
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(mindcraft-skills): hard timeout on every skill call
mc_avoid_enemies (and 7 other tools) wrapped only in safeCall without a
withTimeout. When mindcraft's underlying pathfinder/pvp goal couldn't be
satisfied, the call never resolved — the Pi tick loop blocked forever.
Observed live: mc_avoid_enemies pending >10 minutes after one mc_observe.
safeCall now takes timeoutMs (default 30s) and wraps withTimeout itself,
so every tool gets a hard ceiling. Per-tool overrides:
- goToPosition / goToNearestBlock: 120s / 90s (unchanged from before)
- defendSelf / avoidEnemies: 45s
- stay: secs*1000 + 10s
- craft / consume / pickup / place: 30s
- equip: 15s
collectBlock still uses its bespoke per-iter 75s loop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(runtime): script-driven reflex daemon + Ink TUI dashboard
Pure-Pi runtime had three failure modes in practice:
- slow: 20-60s per decision because LLM was in the hot path
- expensive: every tick (defend, eat, idle) paid for a reasoning pass
- invisible: required tmux capture-pane to know what the bot was doing
New runtime/ layer is a long-running Node daemon that owns the MC
connection, ticks a priority-ordered reflex chain (defend > eat > sleep
> idle) with NO LLM in the hot path, and exposes status + commands over
a Unix-socket IPC. tui/ is an Ink dashboard that attaches over IPC and
can detach freely — multiple TUI clients can connect at once.
Pi/Codex are still available, but as on-demand escalation: TUI hotkey
'a' spawns `pi -p "<prompt>"` as a subprocess and streams stdout into
the dashboard. The self-improvement loop (proposals → operator approval
→ Pi-driven patch → hot reload) is documented in docs/runtime.md but
not yet wired.
Reflex bodies are stubs today — they log decisions but don't drive
Mineflayer actions yet. The priority chain, IPC contract, and TUI are
fully working; subsequent commits will fill in defend/eat/sleep bodies
and wire automatic escalation.
Run with `npm run bot` + `npm run tui`. Pi-only fallback stays at
`npm run agent`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>