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,