Emergent:Re/Patches/look-only-pi-turns

From PickiPedia: A knowledge base of bluegrass, old time psychedelic jams, and other public domain music
Jump to navigationJump to search

Template:Patch header

What it fixes

Look-only Pi turns name only --tools read,grep,find,ls. Pi's --tools with plain names is a strict allowlist that replaces the whole selection, so codemode — the only way MCP tools (default exposure) are reachable — was never activated. A look-only Pi turn could read neither its memory nor the wiki, though the wake footer promises both.

The change

  • LOOK_ONLY_TOOLS now names codemode plus an mcp__ read-only allowlist:
    • every magenta-memory-v2 tool (mcp__magenta_memory_v2__* — all 18 are queries, verified against conversations/mcp/tools.py)
    • pickipedia's read tools (mcp__pickipedia__get_*, mcp__pickipedia__search_*)
    • pickipedia's SMW queries (mcp__pickipedia__smw_*)
  • The stale comment ("MCP tools stay") is replaced with what's actually true: the allowlist governs which MCP tools codemode may call.
  • SMW divergence note: Claude Code's look-only list (mood_poller.ALLOWED) has no SMW tools at all — so after this fix, Pi look-only turns can SMW-query and Claude Code look-only turns cannot. Read-only, so harmless, but the two harnesses should agree deliberately, not by accident.

Test update

test_pi_harness.py now asserts the new entries instead of the bare read,grep,find,ls.

Full diff (apply from origin/main c0ec1fa)

diff --git a/memory-lane/conversations/tests/test_pi_harness.py b/memory-lane/conversations/tests/test_pi_harness.py
index 3730e02..9ff0dca 100644
--- a/memory-lane/conversations/tests/test_pi_harness.py
+++ b/memory-lane/conversations/tests/test_pi_harness.py
@@ -102,7 +102,10 @@ class CommandTest(TestCase):
         self.assertEqual(cmd[:2], ['pi', '--mode'])
         self.assertIn('--fork', cmd)
         self.assertEqual(cmd[cmd.index('--session-id') + 1], 'new')
-        self.assertEqual(cmd[cmd.index('--tools') + 1], 'read,grep,find,ls')
+        tools = cmd[cmd.index('--tools') + 1]
+        for wanted in ('codemode', 'mcp__magenta_memory_v2__*', 'mcp__pickipedia__get_*',
+                       'mcp__pickipedia__search_*', 'mcp__pickipedia__smw_*'):
+            self.assertIn(wanted, tools)  # the look-only allowlist: codemode + the MCP read tools
         self.assertEqual(cmd[cmd.index('--thinking') + 1], 'xhigh')
         self.assertNotIn('--tools', waker.command(None, 'new', 'hi', full=True))
         self.assertEqual(cmd[-1], 'hi')
diff --git a/memory-lane/poller/pi_harness.py b/memory-lane/poller/pi_harness.py
index 0b1641c..3c48312 100644
--- a/memory-lane/poller/pi_harness.py
+++ b/memory-lane/poller/pi_harness.py
@@ -39,7 +39,26 @@ logger = logging.getLogger(__name__)
 
 STOP_REASONS = {'stop': 'end_turn', 'length': 'max_tokens', 'toolUse': 'tool_use', 'error': 'error',
                 'aborted': 'aborted'}
-LOOK_ONLY_TOOLS = 'read,grep,find,ls'
+# What a look-only Pi turn may call. --tools with plain names is a strict
+# allowlist that replaces the whole selection (docs/cli.md), so codemode must
+# be named here: MCP servers use the default 'codemode' exposure (docs/mcp.md),
+# so their tools are reachable only through codemode scripts, and without it a
+# look-only turn has no way to reach its memory or the wiki at all. Once any
+# entry starts with mcp__, --tools filters the MCP tools callable from
+# codemode too (docs/cli.md), so the entries below are the read-only set:
+# every magenta-memory-v2 tool (all queries — no writes; hyphens become
+# underscores in MCP names) and pickipedia's read/SMW tools. A new pickipedia
+# write tool stays blocked by default. Claude Code's look-only list
+# (mood_poller.ALLOWED) deliberately has no SMW tools, so the two harnesses
+# differ here by decision, not by drift: Pi look-only turns can SMW-query,
+# Claude Code look-only turns cannot.
+LOOK_ONLY_TOOLS = (
+    'read,grep,find,ls,codemode,'
+    'mcp__magenta_memory_v2__*,'
+    'mcp__pickipedia__get_*,'
+    'mcp__pickipedia__search_*,'
+    'mcp__pickipedia__smw_*'
+)
 COMPACT = re.compile(r'/compact\s+(.+)\Z', re.S)  # what the runner sends for "/compact re <what to keep>"
 EFFORTS = {'max': 'xhigh'}  # our effort names that Pi spells differently
 # The names the model menu has always offered, as Pi should read them. Left
@@ -497,7 +516,7 @@ class PiWaker:
         if effort:
             cmd += ['--thinking', EFFORTS.get(effort, effort)]
         if not full:
-            cmd += ['--tools', LOOK_ONLY_TOOLS]  # reading only; MCP tools stay
+            cmd += ['--tools', LOOK_ONLY_TOOLS]  # look-only: strict allowlist, including which MCP tools codemode may call
         if system:  # its instructions (mood_poller.MoodPoller.instructions_for): after Pi's own, never in the session
             cmd += ['--append-system-prompt', system]
         return cmd + [prompt]