personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

providers/cli: name the shell tool to non-write cogitate runs

Non-write cogitate talents (e.g. digest) were inventing a 'sol' tool name instead of routing through their provider's actual shell tool, so the sol commands their prompts instructed never ran. Add a shared system-instruction hint that names each provider's shell tool and warns against inventing one. Gated to non-write cogitate paths only; talent prompts unchanged.

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

+92 -14
+20 -8
tests/test_anthropic_cli.py
··· 20 20 return importlib.reload(importlib.import_module("think.providers.anthropic")) 21 21 22 22 23 - def _assert_write_mode_bypasses_restrictions(make_runner): 23 + def _assert_write_mode_bypasses_restrictions(make_runner, config_override=None): 24 24 provider = _anthropic_provider() 25 25 MockCLIRunner = make_runner() 26 + config = {"prompt": "hello", "model": "claude-sonnet-4", "write": True} 27 + if config_override: 28 + config.update(config_override) 26 29 with ( 27 30 patch("think.providers.anthropic.CLIRunner", MockCLIRunner), 28 31 patch("think.providers.anthropic.check_cli_binary"), 29 32 ): 30 - asyncio.run( 31 - provider.run_cogitate( 32 - {"prompt": "hello", "model": "claude-sonnet-4", "write": True}, 33 - lambda e: None, 34 - ) 35 - ) 33 + asyncio.run(provider.run_cogitate(config, lambda e: None)) 36 34 cmd = MockCLIRunner.last_instance.cmd 37 35 assert cmd[cmd.index("--permission-mode") + 1] == "plan" 38 36 assert "--allowedTools" not in cmd 37 + return MockCLIRunner.last_instance 39 38 40 39 41 40 @pytest.fixture ··· 409 408 cmd = MockCLIRunner.last_instance.cmd 410 409 assert cmd[cmd.index("--permission-mode") + 1] == "plan" 411 410 assert cmd[cmd.index("--allowedTools") + 1] == "Bash(sol *)" 411 + system_prompt = cmd[cmd.index("--system-prompt") + 1] 412 + assert "through the `Bash` tool" in system_prompt 413 + assert "Do not invent or call a tool literally named `sol`." in system_prompt 412 414 413 415 def test_write_mode_bypasses_restrictions(self): 414 - _assert_write_mode_bypasses_restrictions(self._mock_runner) 416 + runner = _assert_write_mode_bypasses_restrictions( 417 + self._mock_runner, 418 + {"system_instruction": "Base system"}, 419 + ) 420 + cmd = runner.cmd 421 + assert "--system-prompt" in cmd 422 + system_prompt = cmd[cmd.index("--system-prompt") + 1] 423 + assert system_prompt == "Base system" 424 + assert ( 425 + "Do not invent or call a tool literally named `sol`." not in system_prompt 426 + )
+28
tests/test_cli_provider.py
··· 16 16 ThinkingAggregator, 17 17 assemble_prompt, 18 18 build_cogitate_env, 19 + cogitate_sol_tool_hint, 19 20 ) 20 21 from think.providers.shared import JSONEventCallback, safe_raw 21 22 ··· 68 69 config = {"prompt": "test", "system_instruction": ""} 69 70 _, system = assemble_prompt(config) 70 71 assert system is None 72 + 73 + def test_cogitate_sol_tool_hint_names_provider_tool_name(self): 74 + for tool_name in ("Bash", "run_shell_command", "bash"): 75 + hint = cogitate_sol_tool_hint(tool_name) 76 + assert tool_name in hint 77 + assert "Do not invent or call a tool literally named `sol`." in hint 78 + assert 'command="sol call activities list"' in hint 79 + 80 + def test_assemble_prompt_appends_sol_tool_hint_when_provided(self): 81 + body, system = assemble_prompt( 82 + {"prompt": "hello", "system_instruction": "Base system"}, 83 + sol_tool_name="Bash", 84 + ) 85 + 86 + assert body == "hello" 87 + assert system is not None 88 + assert system.startswith("Base system") 89 + assert "through the `Bash` tool" in system 90 + 91 + def test_assemble_prompt_does_not_append_hint_when_not_provided(self): 92 + body, system = assemble_prompt( 93 + {"prompt": "hello", "system_instruction": "Base system"}, 94 + sol_tool_name=None, 95 + ) 96 + 97 + assert body == "hello" 98 + assert system == "Base system" 71 99 72 100 73 101 # ---------------------------------------------------------------------------
+7 -1
tests/test_google_cli.py
··· 30 30 idx = cmd.index("--approval-mode") 31 31 assert cmd[idx + 1] == "yolo" 32 32 assert "--policy" not in cmd 33 + return MockCLIRunner.last_instance 33 34 34 35 35 36 class TestTranslateGemini: ··· 344 345 assert cmd[idx + 1] == "yolo" 345 346 policy_idx = cmd.index("--policy") 346 347 assert cmd[policy_idx + 1].endswith("policies/cogitate.toml") 348 + prompt_text = MockCLIRunner.last_instance.prompt_text 349 + assert "through the `run_shell_command` tool" in prompt_text 350 + assert "Do not invent or call a tool literally named `sol`." in prompt_text 347 351 348 352 def test_write_mode_uses_yolo_approval(self): 349 - _assert_write_mode_uses_yolo_approval(self._mock_runner) 353 + runner = _assert_write_mode_uses_yolo_approval(self._mock_runner) 354 + prompt_text = runner.prompt_text 355 + assert "Do not invent or call a tool literally named `sol`." not in prompt_text 350 356 351 357 def test_sandbox_none(self): 352 358 provider = _google_provider()
+4 -1
think/providers/anthropic.py
··· 250 250 try: 251 251 check_cli_binary("claude") 252 252 253 - prompt_body, system_instruction = assemble_prompt(config) 253 + prompt_body, system_instruction = assemble_prompt( 254 + config, 255 + sol_tool_name="Bash" if not config.get("write") else None, 256 + ) 254 257 255 258 cmd = [ 256 259 "claude",
+21 -1
think/providers/cli.py
··· 44 44 # --------------------------------------------------------------------------- 45 45 46 46 47 - def assemble_prompt(config: dict[str, Any]) -> tuple[str, str | None]: 47 + def cogitate_sol_tool_hint(tool_name: str) -> str: 48 + """Return the shell-tool hint for non-write cogitate runs.""" 49 + return ( 50 + "When the instructions tell you to run `sol ...` commands, invoke them " 51 + f"through the `{tool_name}` tool. Example: " 52 + f'`{tool_name}(command="sol call activities list")`. ' 53 + "Do not invent or call a tool literally named `sol`." 54 + ) 55 + 56 + 57 + def assemble_prompt( 58 + config: dict[str, Any], 59 + *, 60 + sol_tool_name: str | None = None, 61 + ) -> tuple[str, str | None]: 48 62 """Combine config fields into a single prompt string and system instruction. 49 63 50 64 Joins transcript, extra_context, user_instruction, and prompt with ··· 67 81 68 82 prompt_body = "\n\n".join(parts) if parts else "" 69 83 system_instruction = config.get("system_instruction") or None 84 + if sol_tool_name: 85 + hint = cogitate_sol_tool_hint(sol_tool_name) 86 + if system_instruction: 87 + system_instruction = f"{system_instruction}\n\n{hint}" 88 + else: 89 + system_instruction = hint 70 90 return prompt_body, system_instruction 71 91 72 92
+4 -1
think/providers/google.py
··· 745 745 746 746 try: 747 747 # Assemble prompt from config fields 748 - prompt_body, system_instruction = assemble_prompt(config) 748 + prompt_body, system_instruction = assemble_prompt( 749 + config, 750 + sol_tool_name="run_shell_command" if not config.get("write") else None, 751 + ) 749 752 750 753 # Gemini CLI has no --system-prompt flag; prepend to prompt body 751 754 if system_instruction:
+4 -1
think/providers/ollama.py
··· 498 498 ) 499 499 500 500 # Assemble prompt from config fields 501 - prompt_body, system_instruction = assemble_prompt(config) 501 + prompt_body, system_instruction = assemble_prompt( 502 + config, 503 + sol_tool_name="bash" if not config.get("write") else None, 504 + ) 502 505 503 506 # OpenCode has no --system-prompt flag; prepend to prompt body 504 507 if system_instruction:
+4 -1
think/providers/openai.py
··· 171 171 # Note: Start event is emitted by agents.py (unified event ownership) 172 172 173 173 # Assemble prompt — Codex has no --system-prompt flag, so prepend it 174 - prompt_body, system_instruction = assemble_prompt(config) 174 + prompt_body, system_instruction = assemble_prompt( 175 + config, 176 + sol_tool_name="bash" if not config.get("write") else None, 177 + ) 175 178 if system_instruction: 176 179 prompt_text = system_instruction + "\n\n" + prompt_body 177 180 else: