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:
@@ -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])}...'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
@@ -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"}
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user