Files
JARVIS/backend/app/agents/isolation/strategy_selector.py

148 lines
4.6 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Literal
from app.agents.registry import load_builtin_registry_indexes
from app.agents.registry.models import CapabilityManifest, PermissionClass, SideEffectScope
IsolationMode = Literal["none", "session", "worktree"]
_WORKTREE_QUERY_MARKERS = (
"code",
"repo",
"repository",
"git",
"worktree",
"branch",
"patch",
"diff",
"refactor",
"build",
"test",
"fix",
"file",
"files",
"python",
"typescript",
"javascript",
"代码",
"仓库",
"分支",
"补丁",
"重构",
"构建",
"测试",
"修复",
"文件",
)
@dataclass(frozen=True)
class IsolationDecision:
mode: IsolationMode
reason: str
tool_names: tuple[str, ...] = ()
capability_ids: tuple[str, ...] = ()
metadata: dict[str, Any] = field(default_factory=dict)
def _capability_metadata(capability: CapabilityManifest | None) -> dict[str, Any]:
if capability is None:
return {}
return {
"capability_id": capability.capability_id,
"tool_name": capability.tool_name,
"permission_class": capability.permission_class.value,
"side_effect_scope": capability.side_effect_scope.value,
"supports_retry": capability.supports_retry,
"idempotent": capability.idempotent,
"safe_for_parallel_use": capability.safe_for_parallel_use,
"requires_confirmation": capability.requires_confirmation,
}
def select_isolation_strategy(
*,
user_query: str,
tool_names: list[str] | tuple[str, ...],
role_value: str,
execution_mode: str | None,
) -> IsolationDecision:
indexes = load_builtin_registry_indexes()
capabilities: list[CapabilityManifest] = []
capability_ids: list[str] = []
for tool_name in tool_names:
capability_id = indexes.capability_id_by_tool_name.get(tool_name)
capability = indexes.capability_by_id.get(capability_id) if capability_id else None
if capability is not None:
capabilities.append(capability)
capability_ids.append(capability.capability_id)
normalized_query = (user_query or "").strip().lower()
has_worktree_query_signal = any(marker in normalized_query for marker in _WORKTREE_QUERY_MARKERS)
has_write_capability = any(cap.permission_class == PermissionClass.WRITE for cap in capabilities)
has_external_capability = any(cap.permission_class == PermissionClass.EXTERNAL for cap in capabilities)
has_non_parallel_capability = any(not cap.safe_for_parallel_use for cap in capabilities)
has_stateful_side_effect = any(
cap.side_effect_scope in {SideEffectScope.LOCAL_STATE, SideEffectScope.DB_WRITE}
for cap in capabilities
)
metadata = {
"role": role_value,
"execution_mode": execution_mode,
"capabilities": [_capability_metadata(capability) for capability in capabilities],
"workspace_strategy": "inline",
"risk_level": "low",
}
if has_worktree_query_signal:
return IsolationDecision(
mode="worktree",
reason="workspace_mutation_signals_detected",
tool_names=tuple(tool_names),
capability_ids=tuple(capability_ids),
metadata={
**metadata,
"workspace_strategy": "ephemeral_worktree",
"risk_level": "high",
},
)
if has_write_capability or has_stateful_side_effect or has_non_parallel_capability:
return IsolationDecision(
mode="session",
reason="stateful_or_non_parallel_tooling",
tool_names=tuple(tool_names),
capability_ids=tuple(capability_ids),
metadata={
**metadata,
"workspace_strategy": "isolated_session",
"risk_level": "medium",
},
)
if execution_mode == "collaboration" or role_value in {"analyst", "librarian"} or has_external_capability:
return IsolationDecision(
mode="session",
reason="context_heavy_or_external_retrieval",
tool_names=tuple(tool_names),
capability_ids=tuple(capability_ids),
metadata={
**metadata,
"workspace_strategy": "isolated_session",
"risk_level": "medium",
},
)
return IsolationDecision(
mode="none",
reason="inline_execution_is_sufficient",
tool_names=tuple(tool_names),
capability_ids=tuple(capability_ids),
metadata=metadata,
)