Skip inherited tool-policy prompt filtering

Return inherited tool prompts unchanged before inventory and per-tool resolution. Custom policies keep the existing filtering path. Add a regression for the fast path and document the ownership contract.

Alessandro committed Aug 23, 2026 at 14:16 UTC a754eed10369a2b7d38fc50e17c5127aab23abe0
3 files changed +34
helpers/tool_policy.py
+3
@@ -209,6 +209,9 @@ def ensure_tool_allowed(
209
210
211 def filter_tool_prompt(agent: Any, prompt_file: str, prompt: str) -> str:
212 + if get_policy(agent)["mode"] != "custom":
213 + return prompt
214 +
215 known_names = _policy_tool_names(agent)
216 names = _prompt_tool_names(prompt_file, prompt, known_names)
217 if names and not any(resolve_tool(agent, name).allowed for name in names):
helpers/tool_policy.py.dox.md
+2
@@ -34,6 +34,8 @@
34 - Missing policy inherits standard access. A custom policy records independent
35 defaults for local/plugin tools and canonical MCP tools; explicit allowed or
36 blocked IDs take precedence over either default.
37 +- Inherited prompt policy returns tool text unchanged without inventorying or
38 + resolving individual tools; only custom policy pays the filtering cost.
39 - The `response` capability is a framework-required invariant: profile policy
40 cannot disable it, and the editor does not list it as a configurable tool.
41 - `vision_load` remains owned by the active chat model's vision configuration;
tests/test_tool_policy.py
+29
@@ -132,6 +132,35 @@ def test_provider_native_schemas_omit_blocked_local_tool(
132 assert [tool["name"] for tool in tools] == ["allowed"]
133
134
135 +def test_inherited_prompt_filter_skips_tool_inventory(monkeypatch, tmp_path: Path):
136 + agent = _Agent(tmp_path)
137 + prompt = "### shell\nRun a command."
138 + policy_reads = 0
139 +
140 + def inherited_policy(_agent):
141 + nonlocal policy_reads
142 + policy_reads += 1
143 + return {
144 + "mode": "inherit",
145 + "default": "allow",
146 + "mcp_default": "allow",
147 + "allowed": [],
148 + "blocked": [],
149 + }
150 +
151 + monkeypatch.setattr(tool_policy, "get_policy", inherited_policy)
152 + monkeypatch.setattr(
153 + tool_policy,
154 + "_policy_tool_names",
155 + lambda _agent: pytest.fail("inherited policy inventoried tools"),
156 + )
157 +
158 + assert tool_policy.filter_tool_prompt(
159 + agent, "agent.system.tool.shell.md", prompt
160 + ) == prompt
161 + assert policy_reads == 1
162 +
163 +
164 def test_required_response_survives_default_block(monkeypatch, tmp_path: Path) -> None:
165 _write_prompt(
166 tmp_path,