feat: per-role tool lists and system prompt overlays
Each role in model_registry.json can now carry two optional keys:
system_append — injected into the system prompt at position 7 (after
memory, closest to the turn) for the active chat_role
tools — explicit tool allow-list; intersected with the user's
access-level filter so it can only restrict, never elevate
No changes needed for existing users — missing keys fall back to current
behavior. Add keys to a role to give it a specialty focus:
"coder": {
"primary": "claude_cli",
"system_append": "You are in code-specialist mode...",
"tools": ["web_search", "file_read", "shell_exec", "scratch_write"]
}
Changes:
- model_registry.py: get_role_config() returns system_append + tools
- context_loader.py: role_append param appended as "--- Role Context ---"
- tools/__init__.py: get_tools_for_role/get_openai_tools_for_role accept
optional tool_list and intersect with access-level filter
- orchestrator_engine.py: tool_list threaded through run/resume/checkpoint
- openai_orchestrator.py: tool_list threaded through run/resume/checkpoint;
_build_client now calls get_openai_tools_for_role instead of returning
unfiltered OPENAI_TOOL_SCHEMAS
- routers/orchestrator.py: pulls role_cfg for chat_role, passes both
role_append and tool_list to context loader and engine
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1100,19 +1100,38 @@ OPENAI_TOOL_SCHEMAS: list[dict] = _build_openai_tools()
|
||||
# Role-filtered tool access
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def get_tools_for_role(role: str) -> tuple[list, dict]:
|
||||
def get_tools_for_role(
|
||||
role: str,
|
||||
tool_list: list[str] | None = None,
|
||||
) -> tuple[list, dict]:
|
||||
"""Return (gemini_tool_declarations, callables_dict) filtered to tools the role can use.
|
||||
|
||||
role — user access level ("user" | "admin"); gates admin-only tools
|
||||
tool_list — optional explicit allow-list from role config (e.g. coder role);
|
||||
intersected with the access-level filter so it can only restrict,
|
||||
never elevate privileges
|
||||
|
||||
Usage in orchestrator:
|
||||
tool_declarations, tool_callables = get_tools_for_role(user_role)
|
||||
tool_declarations, tool_callables = get_tools_for_role(user_role, tool_list)
|
||||
"""
|
||||
allowed = {name for name in _CALLABLES if _role_allowed(name, role)}
|
||||
if tool_list is not None:
|
||||
allowed &= set(tool_list)
|
||||
decls = [d for d in _ALL_DECLARATIONS if d.name in allowed]
|
||||
callables = {k: v for k, v in _CALLABLES.items() if k in allowed}
|
||||
return [types.Tool(function_declarations=decls)], callables
|
||||
|
||||
|
||||
def get_openai_tools_for_role(role: str) -> list[dict]:
|
||||
"""Return OpenAI tool schemas filtered to tools the role can use."""
|
||||
def get_openai_tools_for_role(
|
||||
role: str,
|
||||
tool_list: list[str] | None = None,
|
||||
) -> list[dict]:
|
||||
"""Return OpenAI tool schemas filtered to tools the role can use.
|
||||
|
||||
role — user access level ("user" | "admin")
|
||||
tool_list — optional explicit allow-list from role config
|
||||
"""
|
||||
allowed = {name for name in _CALLABLES if _role_allowed(name, role)}
|
||||
if tool_list is not None:
|
||||
allowed &= set(tool_list)
|
||||
return [t for t in OPENAI_TOOL_SCHEMAS if t["function"]["name"] in allowed]
|
||||
|
||||
Reference in New Issue
Block a user