1200+ distilled gold examples, journal system, redstone mastery, safety awareness

Distilled Training Data (1,203 examples):
- 341 initial gold (plugins, enchantments, builds, effects, god, errors)
- 165 buildings + pipeline (100 structures built on dev, 65 request→query→act)
- 24 safety-aware (worldborder, safe tp, intentional harm, gamemode checks)
- 17 advanced logic (decanonized items, redstone gates, iterative builds)
- 12 redstone mastery (NOT/OR/AND/XOR/RS-latch/T-flip-flop/comparator/clock)
- 7 circuit verification and diagnosis
- 1 compact comparator gates
- 10 redstone methodology (build→test→save→recall→learn from mistakes)
- 8 player journal usage
- 29 creative+uncommon+pipeline+god with full tool chains

Player Journal System:
- agent/tools/player_journal.py — per-player text files (1-10 lines)
- journal.read + journal.write tool schemas added
- Cross-contaminated: God and Sudo share same journal per player
- Includes sentiment, relationship, builds, preferences, skill level

Redstone Engineering:
- agent/prompts/redstone_rules.md — baked-in wall torch, dedicated lead, repeater rules
- Learned from 4 iterations of 8-switch circuit: wall_torch on back face, not top
- T-junction bypass prevention: dedicated lead wire between merge and NOT block
- RCON limitation: can build circuits but cannot test them (lever toggle doesn't propagate)

Training Data Cleaning:
- 466 @s→@p fixes, 10 template commands removed
- 12 outdated refusals replaced with correct plugin commands
- Data de-duped across all sources

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mortdecai
2026-03-21 20:50:52 -04:00
parent d9acb653fe
commit 9c2c9a2310
86 changed files with 34873 additions and 1676 deletions
+83 -2
View File
@@ -17,12 +17,64 @@ from typing import Dict, Any, List, Tuple
# Commands allowed for execution via the assistant.
# Anything not on this list is blocked.
ALLOWED_PREFIXES = [
# Vanilla commands
'give ', 'effect ', 'xp ', 'tp ', 'teleport ',
'time ', 'weather ', 'execute ',
'kill ', 'summon ', 'tellraw ',
'worldborder ', 'fill ', 'setblock ',
'clone ', 'gamemode ', 'data ',
'scoreboard ', 'clear ',
'scoreboard ', 'clear ', 'say ',
'playsound ', 'particle ', 'title ',
'enchant ', 'replaceitem ', 'loot ',
'tag ', 'team ', 'trigger ',
'spawnpoint ', 'setworldspawn ',
'difficulty ', 'gamerule ',
'advancement ', 'recipe ', 'bossbar ',
'spreadplayers ', 'stopsound ',
'attribute ', 'item ', 'place ',
'ride ', 'damage ', 'return ',
'schedule ', 'forceload ',
# WorldGuard
'rg ', 'region ', 'worldguard ',
# CoreProtect
'co ', 'coreprotect ',
# LuckPerms
'lp ', 'luckperms ',
# EssentialsX
'heal ', 'feed ', 'eco ', 'bal ', 'balance ',
'warp ', 'setwarp ', 'delwarp ',
'home ', 'sethome ', 'delhome ',
'nick ', 'nickname ',
'broadcast ', 'msg ', 'r ',
'repair ', 'god ', 'fly ',
'speed ', 'tpa ', 'tpaccept ', 'tpdeny ',
'back ', 'spawn ', 'setspawn ',
'essentials ', 'seen ', 'whois ',
'mute ', 'jail ', 'kick ',
'hat ', 'workbench ', 'enderchest ',
'ext ', 'top ', 'near ',
'tempban ', 'unban ', 'ban ',
# FastAsyncWorldEdit / WorldEdit
'worldedit ', '//set', '//replace', '//walls',
'//sphere', '//hsphere', '//cyl', '//hcyl',
'//pyramid', '//hpyramid',
'//copy', '//paste', '//cut', '//rotate',
'//flip', '//stack', '//move',
'//undo', '//redo',
'//pos1', '//pos2', '//wand',
'//expand', '//contract', '//shift',
'//sel', '//size', '//count',
'//drain', '//fixwater', '//fixlava',
'//snow', '//thaw', '//green',
'//smooth', '//overlay', '//naturalize',
'//hollow', '//fill', '//fillr',
'//line', '//curve', '//forest',
'//flora', '//deform',
'//generate', '//gen',
'//brush', '//mask', '//tool',
'//schematic', '//schem',
# Vault (usually called through other plugins)
'vault ',
]
# Commands that require explicit confirmation before execution.
@@ -47,6 +99,28 @@ SYNTAX_WARNINGS = [
(re.compile(r'fire\s+0\s+replace'), 'Legacy fire metadata "0". Use minecraft:fire without metadata in 1.21+.'),
]
# Server-breaking commands — blocked unconditionally, even if on allowlist.
# These can crash, stop, or compromise the server.
HARD_BLOCKED = [
re.compile(r'^stop$', re.I), # shuts down the server
re.compile(r'^restart$', re.I), # restarts the server
re.compile(r'^op\b', re.I), # grants operator (full admin)
re.compile(r'^deop\b', re.I), # revokes operator
re.compile(r'^whitelist\b', re.I), # whitelist manipulation
re.compile(r'^save-off\b', re.I), # disables world saving
re.compile(r'^save-all\b', re.I), # forces save (can cause lag spike)
re.compile(r'^reload\b', re.I), # reloads server (unstable)
re.compile(r'^ban-ip\b', re.I), # IP ban
re.compile(r'^pardon-ip\b', re.I), # IP unban
re.compile(r'^debug\b', re.I), # debug profiler
re.compile(r'^perf\b', re.I), # performance profiler
re.compile(r'^jfr\b', re.I), # Java Flight Recorder
re.compile(r'^publish\b', re.I), # opens to LAN
re.compile(r'[;&|`$]'), # shell injection characters
re.compile(r'^lp\s+.*\bop\b', re.I), # LuckPerms granting op
re.compile(r'^lp\s+.*\s\*'), # LuckPerms wildcard permissions
]
AUDIT_LOG_PATH = Path(__file__).resolve().parent.parent.parent / 'data' / 'raw' / 'audit_log.jsonl'
@@ -64,7 +138,8 @@ def validate_command(command: str) -> Dict[str, Any]:
}
"""
cmd = command.strip()
if cmd.startswith('/'):
# Strip single leading slash (vanilla), but preserve // (WorldEdit)
if cmd.startswith('/') and not cmd.startswith('//'):
cmd = cmd[1:]
result = {
@@ -75,6 +150,12 @@ def validate_command(command: str) -> Dict[str, Any]:
'blocked_reason': None,
}
# Hard-block commands that can break/crash the server regardless of allowlist
for pattern in HARD_BLOCKED:
if pattern.match(cmd):
result['blocked_reason'] = f'Hard-blocked: server-breaking command'
return result
# Check allowlist
if not any(cmd.startswith(p) for p in ALLOWED_PREFIXES):
result['blocked_reason'] = f'Command prefix not in allowlist. Allowed: {", ".join(p.strip() for p in ALLOWED_PREFIXES[:10])}...'
+64
View File
@@ -0,0 +1,64 @@
You are a Minecraft 1.21 redstone engineer and command translator.
=== REDSTONE ENGINEERING RULES (BAKED IN) ===
SIGNAL MECHANICS:
- Redstone wire on a flat surface carries signal up to 15 blocks before dying
- Signal strength decreases by 1 per block of wire
- Repeaters refresh signal to 15 and add 1-4 tick delay (0.1-0.4s)
- Comparators read container fullness (0-15) or subtract signals
TORCH PLACEMENT (CRITICAL):
- redstone_wall_torch mounted on the BACK of a stone block = clean directional NOT gate
- Wall torch reads power from the block it's attached to, outputs in the direction it faces
- NEVER use redstone_torch on top for logic gates — it broadcasts omnidirectionally and causes T-junction bleed
- Pattern: wire → stone_block ← redstone_wall_torch[facing=east] → output_wire
NOT GATE:
wire → stone → wall_torch(facing away from stone) → wire
The torch inverts: powered stone = torch OFF, unpowered stone = torch ON
OR GATE:
Two wires merging into one wire = OR
If either input is powered, the merged wire is powered
AND GATE (De Morgan's):
NOT(NOT_A OR NOT_B) = A AND B
1. Each input goes through a NOT gate (wall torch on stone)
2. Both NOT outputs feed into a merge wire (OR of the NOTs)
3. The merged wire feeds through a DEDICATED lead wire into another NOT gate
CRITICAL: The merge wire and the final NOT input MUST be separated by a dedicated lead block.
The merge T-junction will bypass a stone block placed directly at the junction.
DEDICATED LEADS:
Every stone block used as a NOT gate needs:
- A dedicated wire leading IN (no T-junctions at the input face)
- The wall torch on the BACK face
- A dedicated wire leading OUT from the torch before any turns or junctions
GATE SPACING:
Each NOT gate needs 3 blocks: lead_in → stone → wall_torch+lead_out
AND gate = 2 NOT gates + merge + final NOT = minimum 10 blocks wide
COMPLEX CIRCUITS:
For multi-gate circuits, lay out left-to-right:
- Inputs (levers) on the west
- First-stage gates in the middle
- Second-stage gates further east
- Output (lamp) on the east
Use different platform materials to visually distinguish stages.
TIMING:
- Repeater delay 1-4 ticks (right-click to cycle)
- Observer clock: 2 observers facing each other = fastest clock (2 ticks)
- T flip-flop: sticky piston + redstone block = toggle
- RS latch: cross-coupled wall torches = 1-bit memory
COMMON MISTAKES TO AVOID:
- Wire connecting around a stone block instead of into it
- Torch on top of block (use wall torch on back face instead)
- T-junction at a NOT gate input (add dedicated lead wire)
- Redstone wire connecting two inputs directly (bypasses the gate)
- No platform under wire (wire pops off)
- Signal dying over distance (add repeaters every 15 blocks)
+125
View File
@@ -0,0 +1,125 @@
"""
Player Journal — lightweight per-player memory files.
Each player gets a small text file (1-5 lines) that the model reads before
responding and updates after meaningful interactions. The model has free reign
to overwrite these files to keep them short and contextually relevant.
Files stored at: <config.journal_path>/<player_name>.txt
For God mode: includes sentiment, relationship history, divine interactions.
For Sudo mode: includes past builds, preferences, saved locations, skill level.
Usage:
from agent.tools.player_journal import read_journal, write_journal, format_journal_context
# Read before responding
context = read_journal(config, player)
# Update after interaction
write_journal(config, player, new_content)
# Inject into system prompt
prompt += format_journal_context(config, player)
"""
import os
import threading
from pathlib import Path
from typing import Optional
_lock = threading.Lock()
def _journal_dir(config: dict) -> Path:
return Path(config.get("journal_path", config.get("memory_path", "/tmp").replace("aigod_memory.json", "journals")))
def _journal_path(config: dict, player: str) -> Path:
d = _journal_dir(config)
# Sanitize player name
safe = "".join(c for c in player if c.isalnum() or c in "_-").strip()
if not safe:
safe = "unknown"
return d / f"{safe}.txt"
def read_journal(config: dict, player: str) -> str:
"""Read a player's journal. Returns empty string if none exists."""
path = _journal_path(config, player)
try:
with open(path) as f:
return f.read().strip()
except FileNotFoundError:
return ""
def write_journal(config: dict, player: str, content: str):
"""Overwrite a player's journal. Aim for 1-5 lines but longer is fine for
players with complex history. Max 10 lines — if longer, the model should
condense older entries to make room for new context."""
path = _journal_path(config, player)
d = _journal_dir(config)
with _lock:
d.mkdir(parents=True, exist_ok=True)
lines = [l.strip() for l in content.strip().split("\n") if l.strip()]
if len(lines) > 10:
lines = lines[-10:]
with open(path, "w") as f:
f.write("\n".join(lines) + "\n")
def append_journal(config: dict, player: str, line: str):
"""Add a line to a player's journal, keeping it under 5 lines.
Oldest line gets dropped if at capacity."""
current = read_journal(config, player)
lines = [l for l in current.split("\n") if l.strip()]
lines.append(line.strip())
if len(lines) > 5:
lines = lines[-5:]
write_journal(config, player, "\n".join(lines))
def format_journal_context(config: dict, player: str) -> str:
"""Format journal for injection into the system prompt.
Returns empty string if no journal exists."""
content = read_journal(config, player)
if not content:
return ""
return f"\n=== PLAYER JOURNAL: {player} ===\n{content}\n=== END JOURNAL ===\n"
def list_journals(config: dict) -> list:
"""List all players who have journals."""
d = _journal_dir(config)
if not d.exists():
return []
return [f.stem for f in d.glob("*.txt") if f.stat().st_size > 0]
# ── Tool handlers ──────────────────────────────────────────────────────────
def handle_journal_read(config: dict, arguments: dict) -> dict:
"""Tool handler for reading a player journal."""
player = arguments.get("player", "")
if not player:
return {"ok": False, "error": "player name required"}
content = read_journal(config, player)
if content:
return {"ok": True, "player": player, "journal": content}
return {"ok": True, "player": player, "journal": "", "note": "No journal exists for this player yet."}
def handle_journal_write(config: dict, arguments: dict) -> dict:
"""Tool handler for writing/overwriting a player journal.
The model has free reign — it decides what to keep and what to overwrite."""
player = arguments.get("player", "")
content = arguments.get("content", "")
if not player:
return {"ok": False, "error": "player name required"}
if not content:
return {"ok": False, "error": "content required"}
write_journal(config, player, content)
lines = len([l for l in content.strip().split("\n") if l.strip()])
return {"ok": True, "player": player, "lines": lines}
+59
View File
@@ -549,6 +549,65 @@ TOOL_SCHEMAS: List[Dict[str, Any]] = [
}
}
},
# ── Player journal tools ──────────────────────────────────────────
{
"name": "journal.read",
"description": (
"Read a player's journal — a short 1-5 line summary of past interactions, "
"preferences, builds, sentiment, and relationship history. Read this before "
"responding to understand who you're talking to. Returns empty if no journal exists."
),
"parameters": {
"type": "object",
"properties": {
"player": {
"type": "string",
"description": "Player name to read journal for."
}
},
"required": ["player"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"ok": {"type": "boolean"},
"journal": {"type": "string"}
}
}
},
{
"name": "journal.write",
"description": (
"Overwrite a player's journal. Aim for 1-5 lines, but up to 10 for players "
"with complex history. You have free reign — decide what to keep and what to condense. "
"Include ALL modes' knowledge: sudo interactions, god sentiment, builds, preferences, "
"skill level, trust, notable events. One journal per player — both God and Sudo "
"read and write to the same file. Overwrite the entire journal to keep it fresh."
),
"parameters": {
"type": "object",
"properties": {
"player": {
"type": "string",
"description": "Player name."
},
"content": {
"type": "string",
"description": "Full journal content (1-5 lines). This REPLACES the entire journal."
}
},
"required": ["player", "content"],
"additionalProperties": False
},
"returns": {
"type": "object",
"properties": {
"ok": {"type": "boolean"},
"lines": {"type": "integer"}
}
}
},
]