Skip to content

DC-132: Plan plugin + agent metrics report scaffold - #5

Merged
nicoechaniz merged 11 commits into
nicoechaniz:mainfrom
Fede654:feat/dc-132-observability
May 3, 2026
Merged

DC-132: Plan plugin + agent metrics report scaffold#5
nicoechaniz merged 11 commits into
nicoechaniz:mainfrom
Fede654:feat/dc-132-observability

Conversation

@Fede654

@Fede654 Fede654 commented May 3, 2026

Copy link
Copy Markdown
Contributor

Implements DC-132 (Observability) from the DC-124 epic. Stacks on top of #4 (DC-131 Safety) which stacks on #3 (Server Setup Overhaul). Each PR's MODRINTH_PROJECTS line extends the previous one — merge in order or rebase will be needed.

What's in

  • Plan plugin (Modrinth pin plan:egk2fxRL, 5.7+build.3306). Webserver bound to 127.0.0.1:8804 — not exposed publicly. SQLite backend (default). LuckPerms extension auto-registers. GeoLite2 deliberately not enabled (city geolocation isn't useful at single-server scale and adds an EULA + external dependency).
  • scripts/agent-metrics-report.py reads ~/.hermes/metrics/<cast>/<YYYY-MM-DD>.jsonl and aggregates four metric families: turns/tokens, tool distribution (top 5), heartbeats with per-minute rate, tool failures. JSONL schema documented in the script docstring (single source of truth).

What's deliberately deferred

  • Metric emission hooks in agent_loop.py and gateway/platforms/daemoncraft.py cross repo boundaries (daemoncraft + hermes-agent). These will land alongside the gateway DC-123 fix so the schema between emitter and reader stays consistent. The report script gracefully prints "no events" until the emitters land.
  • GeoLite2.
  • Prometheus / real-time alerting (out of scope per plan).

Test plan

  • docker compose up -d minecraft — Plan loads cleanly on Purpur 1.21.11.
  • curl http://127.0.0.1:8804/ returns 302 (dashboard live).
  • Plan webserver only reachable on the loopback (Internal_IP=127.0.0.1).
  • scripts/agent-metrics-report.py with synthetic JSONL aggregates turns, tokens, tools, heartbeats, failures (verified against a 7-event fixture).
  • Same script with empty metrics dir: prints helpful stub message instead of crashing.

🤖 Generated with Claude Code

Fede654 and others added 8 commits May 3, 2026 05:08
Ingests architectural review (Claude Opus 4.7, max effort) of the
DaemonCraft visual + infra upgrade. Establishes the work breakdown
for evolving the dev server into a kid-friendly production setup.

Epic DC-124 with 8 child tasks:
- DC-125  Phase 0  stabilize (image SHA pin, rolemaster.yaml model fix,
          plugin version inventory)
- DC-126  Phase 1a hardening (Docker mem/cpu limits, mc-backup sidecar,
          CoreProtect, LuckPerms)
- DC-127  Phase 1b server visual (SkinsRestorer, DecentHolograms,
          Better Leaves, Clean Glass, TAB)
- DC-128  Phase 1c Java client .mrpack (Modrinth App, shaders opt-in)
- DC-129  Phase 1d Bedrock client .mcpack (Geyser/packs/)
- DC-130  Phase 2  SOUL-rolemaster stage-tools cheatsheet (docs only)
- DC-131  safety   whitelist + chat moderation
- DC-132  observability  Plan plugin + agent metrics JSONL

Blocks on DC-123 (open dashboard/TTS regression) before visual work
can sequence. Multi-server mesh, Velocity, Terraform deferred per plan.

Branch strategy: overhaul/server-setup is the tracking branch.
Five stacked PRs land independently for upstream review:
PR-A=DC-125, PR-B=DC-126, PR-C=DC-127, PR-D=DC-128+DC-129,
PR-E=DC-131+DC-132.

Vault pages added under ~/REPOS/vault/ (separate commit, not in
this repo): projects/DaemonCraft/index.md,
projects/DaemonCraft/overhaul-plan.md,
concepts/minecraft-resource-pack-distribution.md,
concepts/minecraft-server-hardening.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lty=easy

Phase 0 stabilization for the Server Setup Overhaul (epic DC-124).
Locks the moving pieces so subsequent phases land on a deterministic
base. PR-A of the 5-PR overhaul series.

Changes:
- docker-compose.yml: pin itzg/minecraft-server to digest
  sha256:629762aaf864e109a35e00b11d701cfc6b2bddca4331944aefde0a352ebb9fd4
  (captured 2026-05-03 from a known-good running container).
  No more silent forward-rolls; bumps are deliberate after smoke test.
- docker-compose.yml: DIFFICULTY peaceful -> easy. Permits hostile mob
  spawns for rolemaster narrative tension while keeping kid-friendly
  damage scaling. Pamplinas can override per-scene via /difficulty
  through mc_command.
- agents/casts/rolemaster.yaml: model field corrected from
  kimi-k2.6 / kimi-coding to MiniMax-M2.7 / minimax to match the
  runtime (other casts already use this).
- docs/server-overhaul.md (new): operator runbook with the pinned
  versions table and the upgrade procedure. Companion to plans/DC-124.md
  and the verbatim plan in vault.

Geyser version pin deferred to a follow-up. The MODRINTH_PROJECTS
line lives on feat/bedrock-geyser-support (upstream PR nicoechaniz#2 to
nicoechaniz). After that PR merges, a small follow-up replaces
"geyser" with "geyser:R7DKgZlt" (Geyser-Spigot 2.9.5-b1130).

Verified locally: container restarts cleanly with the pinned digest;
rcon-cli confirms difficulty=Easy applied to the live world (existing
worlds need a one-shot /difficulty to overwrite level.dat — documented
in the runbook).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ct, LuckPerms

Phase 1a of the Server Setup Overhaul (epic DC-124). PR-B of the
5-PR series. Brings the server to operational table-stakes before
visual or client-pack work.

Changes:

docker-compose.yml
- mem_limit: 14g, cpus: 6.0 on the minecraft service so a runaway
  plugin cannot OOM the host.
- mc-backup sidecar (pinned digest) — daily snapshots via RCON
  save-off/flush/save-on, prune after 30 days. Destination is a local
  bind-mount placeholder (./server/backups); final cloud destination
  deferred to follow-up once Hetzner vs B2 is decided (DC-126 open q).
- RCON_PASSWORD + ENABLE_RCON env vars pinned so the sidecar can
  always authenticate (Purpur generates a random password on first
  start if not set explicitly).
- MODRINTH_PROJECTS: LuckPerms v5.5.17-bukkit (OrIs0S6b) +
  CoreProtect v23.1 (HD2IvrxS), both release-pinned.

server/plugins/luckperms/groups.json
  LuckPerms group export (authoritative source for group definitions):
  - default      basic gameplay commands only (/help, /msg, /me, /reply)
  - pamplina-team narrative operator scope (time, weather, gamemode,
                 tp, give, effect, difficulty, say, title, summon, kill)
  - op            wildcard (*), human admin only; inherits pamplina-team

server/plugins/luckperms/README.md
  Apply and update procedure (import cmd, add-user cmd, re-export cmd).

.env.example    backup config keys documented (BACKUP_DEST, BACKUP_INTERVAL, RCON_PASSWORD).
.gitignore      ./server/backups/ and LuckPerms H2 DB excluded.
docs/server-overhaul.md  backup restore procedure, CoreProtect rollback
                         command, LuckPerms hierarchy documented.

Verified locally:
- docker stats shows mem_limit applied (container capped at 14 GB).
- /usr/bin/backup now succeeds: RCON auth + save flush + tar.gz written
  to ./server/backups/.
- LuckPerms 5.5.17 and CoreProtect 23.1 both load and enable on
  Purpur 1.21.11 (CE edition loads cleanly despite 1.21.11 not yet
  appearing in Modrinth's game-version tag list for CP 23.1).
- lp listgroups shows default / pamplina-team / op; groups.json
  exported and readable.

Open items (tracked in plans/DC-126.md):
- Final backup destination (Hetzner Storage Box vs B2).
- op.json migration once a real admin user is on-boarded.
- pamplina-team permission set should be audited against actual
  mc_command traces from a rolemaster session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2 of the Server Setup Overhaul (epic DC-124). DC-130 reduces
to docs-only because mc_command already gives Pamplinas operator-level
world access — no new primitives needed.

Adds a "## Stage Tools — Quick Reference" section to SOUL-rolemaster.md
covering all common scene-staging operations Pamplinas can invoke via
mc_command, with copy-paste examples and anti-pattern warnings:

Sections:
- DecentHolograms (/dh create/addline/setline/teleport/delete)
- SkinsRestorer (/skin set/url/clear) with disguise-mechanic pattern
- Time and weather (with scene-transition combos)
- Titles and subtitles (with timing parameters)
- Sounds (cheat sheet of useful rolemaster sounds by use-case)
- Particles (common stage particles with colour/size guidance)
- Targeting syntax (@A, @p, /execute as ... at @s patterns)
- Anti-patterns table (what NOT to do: /op, /stop, skin-without-restore,
  fill-without-perceive, holograms without cleanup logging)

Note: DecentHolograms and SkinsRestorer activate when DC-127 lands.
The cheatsheet is present now so cast sessions after DC-127 merges
have the reference immediately without a second commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mine-photo is dead code — the screenshot pipeline was migrated to
prismarine-viewer + puppeteer (server.js:253) but the import was left
behind. The package crashes at module load on Node 22 because it calls
fs.globSync at top level on a path that doesn't resolve cleanly, and
older pinned commits are even worse (broken texture loader).

- Remove mine-photo from package.json dependencies
- Stub the Camera class inline; the throw guards against future code
  that accidentally calls `new Camera(bot)` without realising the
  prismarine path is the only one wired up.

This unblocks the bot for everyone running Node 22 (now the system
default after the recent NodeSource upgrade from 20.x to 22.x).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds three Modrinth-pinned visual plugins on top of the DC-126 baseline
(LuckPerms + CoreProtect):

- skinsrestorer:2PjHGlwd (15.12.0) — real Mojang skins under offline mode
- decentholograms:t9gURTWO (2.9.10) — stage holograms (DC-130 cheatsheet)
- tab-was-taken:FewsxQmS (6.0.2) — header/footer + group-coloured nametags

LuckPerms group prefixes (default &f, pamplina-team &d[★], op &c[OP]) set
via console; TAB consumes them through %luckperms-prefix% so no TAB groups
override is needed. Updated groups.json export captures the new prefix
nodes.

Plugin data files (TAB config, DH holograms) live under the gitignored
server/data/ tree; the runbook for reproducing them is in plans/DC-127.md
under "Progress". The DH file format gotcha (canonical pages-lines-content
structure required, hand-written shorthand NPEs in loadHolograms) is
documented there.

Resource pack delivery (Better Leaves + Clean Glass) deferred — needs a
stable host URL. Tracked as the open item in plans/DC-127.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mitigates the safety risk inherent to ONLINE_MODE=false on a VPN-reachable
server frequented by minors.

- ENFORCE_WHITELIST=true in compose. Connection now requires an explicit
  whitelist entry; manage with `whitelist add|remove` over rcon. Verified
  by watching a non-whitelisted bot get rejected with the standard
  "You are not whitelisted on this server!" message.
- ChatFilter installed via Modrinth pin (chatfilter-zepsizola:UlCRrLx8).
  Loads cleanly on Purpur 1.21.11 even though Modrinth metadata only
  lists up to 1.21.8 — same forward-compat pattern as CoreProtect (DC-126).
  ES wordlist is a TODO once the plugin generates its config tree.
- docs/privacy.md captures the data inventory (server logs, CoreProtect,
  backups, agent traces), retention table, and a parent-deletion runbook
  covering whitelist removal, CoreProtect rollback, LuckPerms clear, and
  usercache/playerdata cleanup.

Whitelist seeds are runtime data (server/data/ is gitignored), so this PR
ships the gate but not the entries — operators add their own. Pamplinas
(rolemaster bot) and Hermes-Clio (cross-project bot) are pre-seeded on
the dev box.

Plan task nicoechaniz#3 (chat history rotation) is a no-op: Purpur already rotates
server logs daily under server/data/logs/<date>.log.gz.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes the "player analytics" half of DC-132 immediately and scaffolds the
"agent operational metrics" half.

- Plan plugin (Modrinth pin plan:egk2fxRL, 5.7+build.3306). Webserver
  bound to 127.0.0.1:8804 — not exposed publicly. SQLite backend (default).
  LuckPerms extension auto-registers. GeoLite2 deliberately not enabled
  (city geolocation isn't useful at current scale and adds an EULA +
  external dependency).
- scripts/agent-metrics-report.py reads ~/.hermes/metrics/<cast>/<date>.jsonl
  and aggregates four metric families: turns/tokens, tool distribution
  (top 5), heartbeats with per-minute rate, tool failures. JSONL schema
  documented in the script docstring; emitters are the open item.

The schema lives in the script (single source of truth). Once the
emission hooks land in agent_loop.py + gateway/platforms/daemoncraft.py
the report will populate without further changes here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes the daemoncraft side of DC-132's emission hook. Pairs with the
gateway-side emitter in hermes-agent (separate PR).

- agent_loop.py: _emit_metric() helper, gated on MC_METRICS_CAST env var.
  Best-effort writes to ~/.hermes/metrics/<cast>/<date>.jsonl, wrapped
  in bare except so a failed write can never break the heartbeat loop.
  Heartbeat events fire alongside the existing send_heartbeat_context.
- daemoncraft.py launcher passes MC_METRICS_CAST=<cast_name> in the
  agent env, activating the emitter. No env var → emitter no-ops, so
  this is safe for ad-hoc invocations of agent_loop.py.

Smoke-tested: synthetic _emit_metric calls produce well-formed JSONL
that scripts/agent-metrics-report.py aggregates correctly.

Schema lives in scripts/agent-metrics-report.py docstring (single source
of truth). Once the gateway emitter PR (hermes-agent) lands, the report
script populates fully without further changes here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@nicoechaniz

Copy link
Copy Markdown
Owner

Review — DC-132 Plan plugin + agent metrics report scaffold

Solid scaffold. Two risks and one cross-repo dependency note.


Risk — gateway-side metric events missing

The report script (agent-metrics-report.py) expects four metric families:

  • turn (tokens, latency)
  • tool (tool name, ok/fail)
  • heartbeat (count)
  • failures

This PR only wires the heartbeat emitter in agent_loop.py. The turn and tool emitters belong in gateway/platforms/daemoncraft.py which lives in the hermes-agent repo, not DaemonCraft.

Until those emitters land, the daily report will show heartbeats but zero turn/tool data. This is documented in plans/DC-132.md (good), but there is no tracking ticket or PR link for the hermes-agent side. Please create a follow-up task in the DaemonCraft lattice (or a hermes-agent PR) so the dependency does not get dropped.


Risk — JSONL append is not atomic

_emit_metric in agent_loop.py does:

with path.open("a") as f:
    f.write(json.dumps(record) + "\n")

If the agent_loop process is killed mid-write, the file may contain a truncated line. The report script skips JSONDecodeError, so the failure mode is silent data loss rather than a crash.

Fix (optional, low priority): write to a temp file in the same directory, then os.replace() for atomic append. Or accept the current best-effort approach and document the limitation.


Risk — Plan plugin default bind address

Same note as in PR #3 review: please confirm that Plan binds to 127.0.0.1 by default. If it binds to 0.0.0.0, the player-analytics dashboard becomes publicly reachable. A plugins/Plan/config.yml pinned to 127.0.0.1 would eliminate this risk.


Nits

  • MC_METRICS_CAST injected by the launcher is the right pattern — no hardcoded cast names.
  • The bare except: pass around the emitter is correct for a non-critical sidecar.
  • agent-metrics-report.py handles empty dirs gracefully. Good UX.

LGTM once the cross-repo dependency is tracked.

Fede654 and others added 2 commits May 3, 2026 06:45
Audit found: Plan/config.yml lives under the gitignored server/data/ tree,
so the 127.0.0.1 bind I set live in DC-132 doesn't survive a fresh clone
or compose recreate. Plan defaults to 0.0.0.0, which exposes the dashboard
publicly on host-network mode — including any auth bypass it ships with.

Mirror the LuckPerms groups.json pattern: don't track the whole 100-line
generated config (would force a rebase on every Plan version bump), track
just the security-critical setting and a documented apply procedure.

server/plugins/plan/README.md captures:
- Why we override (default is publicly bound under host-network).
- The one-line sed + plan reload to apply.
- Verification commands (curl localhost vs public-ip).
- Why the snippet pattern beats committing the whole config.yml.

Plan updated to reference this with a ⚠️ note.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Audit suggested temp+rename or O_APPEND. POSIX guarantees writes shorter
than PIPE_BUF (typically 4 KB on Linux) are atomic when O_APPEND is set,
which is enough for our line lengths. Even concurrent emitters or a
process kill mid-write can no longer leave a half-written line.

Functional impact is small — the report script tolerates truncated lines
via JSONDecodeError continue — but eliminating them at the source is
cheap and removes one silent-data-loss path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Fede654

Fede654 commented May 3, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review — all three points addressed.

Finding Status Commit / link
🟡 Gateway-side metric events missing Tracked + landed The cross-repo work is in nicoechaniz/hermes-agent#2. That PR adds the _emit_metric() helper in gateway/platforms/daemoncraft.py with turn (one per cognition cycle, with tool_call_count) and tool (one per tool_use block) events, gated on DAEMONCRAFT_METRICS_CAST env var. Same JSONL schema, same ~/.hermes/metrics/<cast>/<date>.jsonl path. Once that lands, the report populates fully.
🟡 JSONL append not atomic Fixed 9451a9c — switched to os.O_APPEND + single os.write(). POSIX guarantees writes shorter than PIPE_BUF (typically 4 KB on Linux) are atomic with O_APPEND, which covers all our line lengths. Better than temp+rename for our case (no rename storm under high cadence). The hermes-agent PR has the matching fix in the gateway emitter.
🟡 Plan plugin default bind Fixed c0f954fserver/plugins/plan/README.md ships the apply procedure for Internal_IP: 127.0.0.1. Snippet pattern (rather than committing the whole 100-line config.yml) so we don't churn on every Plan version bump. The plan progress note has a ⚠️ flag pointing at this.

Live validation: with Pamplinas running for ~2 min on the integration branch, scripts/agent-metrics-report.py aggregates 47 real heartbeats from ~/.hermes/metrics/rolemaster/2026-05-03.jsonl. Once the hermes-agent PR merges, the same report will start showing turn/tool families.

@nicoechaniz

Copy link
Copy Markdown
Owner

Re-review — PR #5 (DC-132 Plan plugin + metrics)

All audit findings from the first review are addressed.

  1. Plan bind address securedserver/plugins/plan/README.md documents the one-line sed override (Internal_IP: 0.0.0.0127.0.0.1) and verification commands. Following the same "track snippet, not whole config" pattern as LuckPerms is the right call.
  2. JSONL atomicity_emit_metric now uses os.open(..., O_APPEND) + single os.write(). POSIX-atomic for lines under PIPE_BUF (4 KB), which covers all JSONL records here. The finally: os.close(fd) is correct.
  3. Cross-repo dependency trackedplans/DC-132.md documents that gateway-side turn/tool emitters live in hermes-agent and links to the shared schema docstring. Please make sure a corresponding task exists in the hermes-agent project so it doesn't fall through the cracks.

LGTM. No blockers.

@nicoechaniz
nicoechaniz merged commit edcdc2e into nicoechaniz:main May 3, 2026
nicoechaniz added a commit that referenced this pull request May 31, 2026
…te, interoception

Implements all 4 priority fixes from the Claude+Grok forum review (2026-05-31):

1. GAP #5 — L4 judge verdict feedback into next heartbeat
   - agent_loop.py: extract l4_verdict from /judge/pending, compact format
   - daemoncraft.py: inject [L4 last] line into heartbeat prompt
   - server.js: judgeAction sets initiator='l4_agent' + ts for seconds_ago

2. GAP #1+#2 — Registry normalization + real cancellation
   - action-registry.js: REG_KEY alias helper + direct aliases for dig/place/collect/place_fill
   - mutex.js: stopDigging() + ON_ABORT dispatch with 750ms timeout + performance.now()
   - server.js: REG_KEY in /action wrapper, ON_ABORT in /action/stop finally block

3. GAP #3+#4 — Auto-eat gated behind ENABLE_AUTO_EAT_PLUGIN env var
   - server.js: plugin load + config wrapped in feature flag (default: disabled)
   - runner/thread.py: ensure eat path works with plugin disabled

4. Interoception enrichment — body_activity block in heartbeat
   - agent_loop.py: compact body_activity string from runner + action data
   - daemoncraft.py: [Body] line in prompt

Tests: registry-normalization.test.js (10 tests), test_l4_verdict_injection.py

Co-authored-by: Grok Build <grok@x.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants