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
+11
View File
@@ -57,6 +57,17 @@ def test_search_empty_query_returns_nothing():
assert asyncio.run(s.search_documents("", _fake_embed)) == []
def test_conversation_project_binding():
s = _store()
assert s.conversation_project("nope") is None # not created yet
s.create_conversation("c1", "projX")
assert s.conversation_project("c1") == "projX"
s.create_conversation("c1", "other") # idempotent: keeps projX
assert s.conversation_project("c1") == "projX"
s.create_conversation("c2")
assert s.conversation_project("c2") == "" # unscoped
def test_conversation_recall_uses_vec_and_matches_brute_force():
s = _store()
if not s.vec_enabled:
+9
View File
@@ -41,6 +41,15 @@ def test_action_tools_registered():
assert names == ["web_search", "remember"]
def test_action_tools_gated_by_consent():
allow = ["search_memory", "web_search", "remember", "fetch_url"]
on = [s["function"]["name"] for s in tools.schemas_for(allow, allow_actions=True)]
off = [s["function"]["name"] for s in tools.schemas_for(allow, allow_actions=False)]
assert set(on) == set(allow) # all pass when actions allowed
assert off == ["search_memory"] # action tools withheld when not
assert tools.is_action("remember") and not tools.is_action("search_memory")
class _FakeManager:
"""Returns a tool_call on the first chat() call, plain content after."""
def __init__(self):