feat: per-conversation project binding + action-tool consent gate

- Conversations bind to a project on creation; RAG scopes to the
  conversation's project, not the global setting.
- Action tools (web_search/fetch_url/remember) are withheld unless
  allow_action_tools is enabled (off by default). Settings toggle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jon
2026-07-23 14:29:40 -05:00
co-authored by Claude Opus 4.8
parent ba6a4ac4e4
commit 492020b547
6 changed files with 91 additions and 10 deletions
+18 -3
View File
@@ -211,9 +211,24 @@ REGISTRY: dict[str, tuple[dict, Callable[..., Awaitable[str]]]] = {
}
def schemas_for(names: list[str]) -> list[dict]:
"""Tool schemas for a playbook's allowlist; unknown names are dropped."""
return [REGISTRY[n][0] for n in (names or []) if n in REGISTRY]
# Tools that act (write local state or reach the network). These require an
# explicit consent gate (settings.allow_action_tools) on top of the per-playbook
# allowlist — a playbook granting one isn't enough on its own.
ACTION_TOOLS = frozenset({"web_search", "fetch_url", "remember"})
def is_action(name: str) -> bool:
return name in ACTION_TOOLS
def schemas_for(names: list[str], allow_actions: bool = True) -> list[dict]:
"""Tool schemas for a playbook's allowlist; unknown names are dropped.
When allow_actions is False, action tools are withheld so the model can't
even call them."""
return [
REGISTRY[n][0] for n in (names or [])
if n in REGISTRY and (allow_actions or not is_action(n))
]
async def dispatch(name: str, args: dict | None) -> str: