Refine vanilla AI prompts and sudo reliability

This commit is contained in:
Claude Code
2026-03-17 19:53:25 -04:00
parent 29c43e2d30
commit 4771d6c03e
4 changed files with 128 additions and 4 deletions
+4
View File
@@ -158,6 +158,10 @@ Main file: `/etc/mc_aigod.json`
| `bug_log_path` | string | File path used for `bug_log` entries |
| `bug_log_event_lines` | int | Number of in-memory interesting events to attach |
| `bug_log_raw_lines` | int | Number of raw tail log lines to attach |
| `bug_log_service_lines` | int | Number of recent AI action lines from service log |
| `tp_safety_enabled` | bool | Auto-protect risky vertical teleports with safety effects |
| `tp_safety_vertical_delta` | number | Relative `~ ~N ~` threshold that triggers safety wrapper |
| `tp_safety_absolute_y` | number | Absolute Y threshold that triggers safety wrapper |
### Current shrink-world example
+5
View File
@@ -112,6 +112,10 @@ This section captures decisions and context accumulated across conversations wit
- **Session gateway (Paper fork only):** The LangGraph-style FastAPI sidecar adds multi-turn memory without requiring a full LangGraph install. Safety enforcement stays in `mc_aigod_paper.py`, not the gateway.
- **Bug intake trigger (2026-03-17):** Added `bug_log <optional description>` chat command in vanilla AI God. It appends structured bug entries to `/var/log/mc_aigod_bug.log` with player, description, recent in-memory events, and recent raw `latest.log` lines, then confirms to the player in chat.
- **God voice update (2026-03-17):** Increased default God persona emphasis on irony, dark humor, and sarcastic one-liners in both command and message system prompts (vanilla + Paper variants) while keeping command strictness unchanged.
- **Paper bug report triage (2026-03-17):** `bug_log` captured a case where a prayer reward path issued vertical teleport (`execute as slingshooter08 run tp slingshooter08 ~ ~10 ~`) and felt unsafe/unintended for a build-oriented prayer; indicates need for pray-path movement guardrails and safer teleport policy.
- **Teleport safety guard (2026-03-17):** Added execution-time TP safety wrapper in vanilla `execute_response`: risky upward teleports (relative delta >= configured threshold or high absolute Y) are prefixed with `slow_falling` + `resistance` effects to prevent accidental lethal falls while keeping punishment/spectacle behavior.
- **Bug-log signal upgrade (2026-03-17):** `bug_log` entries now include filtered raw log lines (RCON connect/disconnect noise removed) plus a new "RECENT AI ACTIONS" section from service logs (`Prayer from`, `Executing RCON`, `SUDO execute`, results) for faster triage.
- **Weather normalization fix (2026-03-17):** added explicit prompt/context rule and command normalizer in vanilla script mapping `weather storm|rainstorm|thunderstorm` -> `weather thunder` before validation/execution.
### Infrastructure decisions
@@ -119,6 +123,7 @@ This section captures decisions and context accumulated across conversations wit
- **mc-godmode.service startup:** Already enabled (`systemctl is-enabled` = enabled), `WantedBy=multi-user.target`, `After=mcsm-daemon.service`. The service was not failing — the MC server itself wasn't auto-starting (fixed by enabling autoStart).
- **shrink-world aigod tail fix (2026-03-17):** `mc-aigod.service` stayed active but stopped reacting to chat after log file recreation because `tail_log()` used a single `open()+readline()` handle. Updated `/usr/local/bin/mc_aigod.py` to detect inode changes/truncation and reopen `latest.log` automatically; restarted `mc-aigod.service` on CT 644.
- **sudo hallucination observed (2026-03-17):** after tail fix, `sudo destroy my surroundings` was parsed and executed as `execute as slingshooter08 run tp @e[type=player,tag=destroyed] ~ ~ ~` and `execute as slingshooter08 run kill @e[type=player,tag=destroyed]`. Both returned empty RCON results; current validator allowed them because prefix-only checks pass and only `give/effect` have syntax repair.
- **Context/prompt refinement (2026-03-17):** updated vanilla sudo/god prompts to emphasize strict 1.21 syntax (including `effect give ...`, no old enchant NBT, weather clear/rain/thunder only), concise JSON outputs, and avoidance of accidental high vertical teleports in benevolent responses; increased vanilla sudo translator token budget from 180 to 260 to reduce truncated JSON on longer kit requests.
- **bug_log deployment status (2026-03-17):** local repo had `bug_log` feature before CT deployment. `/usr/local/bin/mc_aigod.py` on CT 644 did not contain `BUG_LOG_PATTERNS` or bible help text until redeployed; pushed updated script + config and restarted `mc-aigod.service` successfully.
### Open threads / next ideas
+115 -4
View File
@@ -254,14 +254,38 @@ def _bug_log_path(config) -> str:
def _read_recent_raw_log_lines(log_path: str, max_lines: int) -> list:
lines = deque(maxlen=max_lines)
lines = deque(maxlen=max_lines * 4)
noise = re.compile(r'RCON (?:Listener|Client)|Thread RCON Client')
try:
with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
for line in f:
lines.append(line.rstrip('\n'))
clean = line.rstrip('\n')
if noise.search(clean):
continue
lines.append(clean)
except Exception as e:
return [f"<unable to read raw server log: {e}>"]
return list(lines)
return list(lines)[-max_lines:]
def _service_log_path(config) -> str:
return config.get("service_log_path", "/var/log/mc_aigod.log")
def _read_recent_service_action_lines(path: str, max_lines: int) -> list:
lines = deque(maxlen=max_lines * 8)
keep = re.compile(
r'Prayer from|SUDO from|Executing RCON:|SUDO execute:|RCON result:|SUDO result:|BUG_LOG from'
)
try:
with open(path, 'r', encoding='utf-8', errors='replace') as f:
for line in f:
clean = line.rstrip('\n')
if keep.search(clean):
lines.append(clean)
except Exception as e:
return [f"<unable to read service log: {e}>"]
return list(lines)[-max_lines:]
def _format_recent_event_lines(max_events: int) -> list:
@@ -281,9 +305,11 @@ def process_bug_log(player: str, description: str, config):
event_count = int(config.get("bug_log_event_lines", 40))
raw_count = int(config.get("bug_log_raw_lines", 120))
service_count = int(config.get("bug_log_service_lines", 30))
recent_events = _format_recent_event_lines(event_count)
raw_lines = _read_recent_raw_log_lines(config.get("log_path", ""), raw_count)
service_lines = _read_recent_service_action_lines(_service_log_path(config), service_count)
bug_path = _bug_log_path(config)
timestamp = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')
@@ -302,6 +328,11 @@ def process_bug_log(player: str, description: str, config):
bf.write("\n".join(recent_events) + "\n")
else:
bf.write("(no recent in-memory events captured)\n")
bf.write("\n-- RECENT AI ACTIONS (SERVICE LOG) --\n")
if service_lines:
bf.write("\n".join(service_lines) + "\n")
else:
bf.write("(no AI service lines available)\n")
bf.write("\n-- RECENT RAW SERVER LOG LINES --\n")
if raw_lines:
bf.write("\n".join(raw_lines) + "\n")
@@ -644,6 +675,8 @@ MOVEMENT:
NEVER use: tp <player> minecraft:the_nether (this is wrong syntax)
WORLD/ENVIRONMENT (affects all players):
SYNTAX: weather <clear|rain|thunder> [duration]
NOTE: 'storm' is invalid; if intent says storm/rainstorm/thunderstorm use thunder.
time set day
time set night
weather clear 6000
@@ -705,9 +738,12 @@ def build_system_prompt(config):
"- Do not ask a player to gather materials they clearly don't have access to or that are rare relative to their current situation.\n"
"- For give commands: use any valid Minecraft 1.21 item ID following the Item Naming Rules. Do not guess item IDs — consult the naming rules and common IDs list.\n"
"- For all other commands: only use forms shown in the Command Palette. Do not invent new command types.\n"
"- Weather values are only clear, rain, or thunder. If you mean storm/rainstorm/thunderstorm, output thunder.\n"
"- Reward humble, genuine prayers. Punish hubris, blasphemy, or naked greed.\n"
"- Repeated greedy demands after recent gifts should usually receive correction (rebuke, debuff, or symbolic punishment), not more rewards.\n"
"- Powerful rewards (netherite, enchanted_golden_apple, totem) must be rare.\n"
"- kill {target} is reserved for extreme blasphemy only.\n"
"- Avoid lethal accidents from vertical teleports. If using a high upward teleport as punishment or spectacle, pair it with slow_falling or resistance unless explicit execution is intended.\n"
"- When angered, chain commands: thunder + lightning + debuffs = divine wrath.\n\n"
"=== COMMAND PALETTE ===\n"
f"{COMMAND_PALETTE}\n"
@@ -828,6 +864,9 @@ COMMANDS_SYSTEM_PROMPT = (
"- kill is reserved for extreme blasphemy only.\n"
"- For give: syntax is always give <player> minecraft:<item_id> <count>\n"
"- Count comes LAST. Namespace prefix minecraft: is REQUIRED.\n"
"- For effects: use 'effect give <player> minecraft:<effect> <seconds> <amplifier>'.\n"
"- For weather use only clear/rain/thunder (NOT storm).\n"
"- Avoid accidental lethal movement in benevolent responses; do not launch players high unless explicitly asked.\n"
"- Beds: white_bed not bed. Logs: oak_log not log. Wool: white_wool not wool.\n"
"- Chain commands for dramatic effect: thunder + lightning + blindness = wrath.\n\n"
+ "=== COMMAND PALETTE ===\n"
@@ -850,7 +889,10 @@ def build_sudo_commands_system_prompt(config=None) -> str:
"- If the request cannot be mapped safely, return commands: [].\n"
"- If player says 'me' or 'my', target the requesting player.\n"
"- You will receive LAST 10 SUDO ACTIONS. Use them for continuity and corrections when the player says previous output was wrong.\n"
"- Keep output concise (usually <= 6 commands) and valid JSON only.\n"
"- For give syntax: give <player> minecraft:<item_id> <count> (count LAST, namespace required)\n"
"- For effect syntax: effect give <player> minecraft:<effect> <seconds> <amplifier> [hideParticles]\n"
"- Never use old NBT enchant syntax like {Enchantments:[...]}; use item[enchantments={...}] only.\n"
"- Return commands only. No commentary.\n"
"\n"
"=== TELEPORT SYNTAX ===\n"
@@ -940,6 +982,7 @@ def build_message_system_prompt(config) -> str:
"Write a single spoken message to all players reacting to this prayer and action.\n"
"Respond with ONLY the message text — no JSON, no quotes, no formatting. "
"Be vivid and dramatic, with occasional godlike sarcasm and irony. Any length is fine.\n"
"For punishments, prefer fear and humiliation over accidental instant death unless destruction is explicitly intended.\n"
)
lore = config.get("god_lore", "")
if lore:
@@ -1210,6 +1253,53 @@ def _tp_inside_worldborder(cmd: str, config) -> bool:
return abs(x - cx) <= limit and abs(z - cz) <= limit
def _tp_safety_enabled(config) -> bool:
return bool(config.get("tp_safety_enabled", True))
def _extract_tp_target_y(cmd: str):
m = re.search(r'\btp\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)', cmd)
if not m:
return None, None
return m.group(1), m.group(3)
def _needs_vertical_tp_safety(resolved_cmd: str, config) -> tuple:
target, ytok = _extract_tp_target_y(resolved_cmd)
if not target or not ytok:
return False, ""
risky_delta = float(config.get("tp_safety_vertical_delta", 8.0))
risky_abs_y = float(config.get("tp_safety_absolute_y", 120.0))
if ytok.startswith("~"):
offs = ytok[1:].strip()
dy = float(offs) if offs else 0.0
if dy >= risky_delta:
return True, target
return False, target
try:
abs_y = float(ytok)
if abs_y >= risky_abs_y:
return True, target
except Exception:
pass
return False, target
def _tp_safety_prefix_commands(resolved_cmd: str, config) -> list:
if not _tp_safety_enabled(config):
return []
needs, target = _needs_vertical_tp_safety(resolved_cmd, config)
if not needs or not target:
return []
return [
f"effect give {target} minecraft:slow_falling 20 0",
f"effect give {target} minecraft:resistance 8 1",
]
def fix_give_command(cmd: str) -> str:
"""
Correct common LLM give command mistakes:
@@ -1274,11 +1364,21 @@ def fix_effect_command(cmd: str) -> str:
return fixed
return cmd
def fix_weather_command(cmd: str) -> str:
"""Normalize weather synonyms to valid Minecraft literals."""
raw = (cmd or "").strip()
fixed = re.sub(r'\bweather\s+(storm|rainstorm|thunderstorm)\b', 'weather thunder', raw, flags=re.IGNORECASE)
if fixed != raw:
log.warning(f"Fixed weather syntax: '{cmd}' -> '{fixed}'")
return fixed
def validate_command(cmd, online_players, fallback_player, config=None):
"""Replace placeholders, auto-fix common give syntax errors, check safe prefix."""
resolved = cmd.replace("{player}", fallback_player).replace("{target}", fallback_player)
resolved = fix_give_command(resolved)
resolved = fix_effect_command(resolved)
resolved = fix_weather_command(resolved)
caps = get_server_capabilities(config) if config else SERVER_CAPABILITIES[DEFAULT_SERVER_TYPE]
prefixes = caps["safe_prefixes"]
if not any(resolved.startswith(p) for p in prefixes):
@@ -1347,6 +1447,17 @@ def execute_response(response, context, config, praying_player=None):
resolved, is_safe = validate_command(cmd, context["online_players"], fallback, config)
if not is_safe:
continue
safety_prefix = _tp_safety_prefix_commands(resolved, config)
for scmd in safety_prefix:
sresolved, safe_ok = validate_command(scmd, context["online_players"], fallback, config)
if not safe_ok:
continue
log.info(f"Executing RCON: {sresolved}")
sresult = rcon(sresolved, config["rcon_host"], config["rcon_port"], config["rcon_password"])
log.info(f"RCON result: {sresult!r}")
time.sleep(0.15)
log.info(f"Executing RCON: {resolved}")
result = rcon(resolved, config["rcon_host"], config["rcon_port"], config["rcon_password"])
log.info(f"RCON result: {result!r}")
@@ -1526,7 +1637,7 @@ def process_sudo(player, prompt, config):
config=config,
fmt="json",
temperature=0.1,
max_tokens=180,
max_tokens=260,
)
parsed = _parse_llm_json(content)
commands = parsed.get("commands") or []
+4
View File
@@ -21,6 +21,10 @@
"bug_log_path": "/var/log/mc_aigod_bug.log",
"bug_log_event_lines": 40,
"bug_log_raw_lines": 120,
"bug_log_service_lines": 30,
"tp_safety_enabled": true,
"tp_safety_vertical_delta": 8,
"tp_safety_absolute_y": 120,
"tp_border_guard_enabled": true,
"worldborder_center_x": 0,
"worldborder_center_z": 0,