simbiot-harness/venv/lib/python3.12/site-packages/strands_harness/interventions.py
Simbiot Harness b60439152b Simbiot Harness v0.1: тактовая лестница на Strands + CQRS/ES-спина
- harness/es.py: append-only event store + проекции (образец — кооп-портал)
- harness/levels.py: уровни 1с/1м/1ч/1д, контекст ∝ масштабу
- harness/scheduler.py: уровневые потоки, идемпотентные тики, absorbed-стабы
- harness/isolated_runner.py + _llm_worker.py: LLM-тик в сабпроцессе
  (глобальный asyncio-рантайм Strands не переживает 2+ create_harness)
- cli.py: init/task/tasks/status/events + requeue (написан самим минутным агентом)
- 9router: отдельный ключ sk-simbiot, модель auto (glm-5.3 первым)
2026-09-29 15:28:50 +00:00

94 lines
4.2 KiB
Python

"""Resolve the harness's ``interventions`` sugar into SDK intervention handlers.
The harness's ``interventions`` option accepts a preset name, a natural-language policy, a Cedar policy
file, an SDK handler instance, or a list of these, and coerces them into the handlers that
``Agent(interventions=...)`` expects. A raw handler instance passes through untouched, so anything
the presets don't cover (a Slack ``ask`` callback, a Cedar principal resolver, custom trust rules)
stays reachable by constructing the SDK handler yourself.
The string grammar is deterministic — no content sniffing:
- a preset keyword (``off``/``ask``/``smart``) maps to a ``HumanInTheLoop`` config,
- a path ending in ``.cedar`` loads a ``CedarAuthorization`` policy,
- any other string is a natural-language risk policy: it becomes the LLM risk classifier's prompt.
Inline Cedar policy text is intentionally *not* auto-detected — it is indistinguishable from prose,
so pass ``CedarAuthorization(policies=...)`` directly for that.
"""
from __future__ import annotations
from collections.abc import Callable
from typing import Any, Literal
from strands.interventions import InterventionHandler
from strands.vended_interventions.hitl import HumanInTheLoop, LLMClassifierConfig
InterventionValue = str | InterventionHandler
InterventionsOption = InterventionValue | list[InterventionValue] | None
# How a preset collects approval: the SDK's ``"stdio"``, a custom callback, or interrupt/resume.
InterventionAsk = Literal["stdio"] | Callable[..., Any] | None
def _cedar_handler(policies: str) -> InterventionHandler:
try:
from strands.vended_interventions.cedar import CedarAuthorization
except ImportError as e:
raise ValueError(
f"Cedar policy {policies!r} needs the optional cedar dependency. "
"Install it with: pip install 'strands-agents[cedar]'."
) from e
return CedarAuthorization(policies=policies)
def _resolve_one(value: InterventionValue, ask: InterventionAsk) -> InterventionHandler | None:
if isinstance(value, InterventionHandler):
return value
if not isinstance(value, str):
raise ValueError(
f"Invalid interventions value {value!r}; expected a preset name, a policy string, or a handler instance."
)
text = value.strip()
if text == "off":
return None
if text == "ask":
return HumanInTheLoop(ask=ask)
if text == "smart":
return HumanInTheLoop(classifier=True, ask=ask)
# ``.cedar`` suffix only: the SDK loader treats a non-``.cedar`` string as inline policy, and
# sniffing file existence would misroute a prose policy that happened to match a filename.
if text.endswith(".cedar"):
return _cedar_handler(text)
# Natural-language policy: the LLM risk classifier judges each call against this prompt and
# escalates a flagged one for approval — like ``smart``, but with your own rubric.
return HumanInTheLoop(classifier=LLMClassifierConfig(system_prompt=value), ask=ask)
def _check_handler_collisions(handlers: list[InterventionHandler]) -> None:
"""Raise if two handlers share a name — the SDK registers at most one per ``name``, so a second
would silently win or be dropped. Different kinds (a Cedar policy plus one human-approval preset)
have different names and coexist; two of the same kind collide."""
seen: set[str] = set()
for handler in handlers:
if handler.name in seen:
raise ValueError(
f"Two interventions share the handler name {handler.name!r}, but an agent registers at "
"most one per name. Layer different kinds (e.g. a Cedar policy plus one human-approval "
"preset), not two of the same kind."
)
seen.add(handler.name)
def resolve_interventions(
value: InterventionsOption,
*,
ask: InterventionAsk = None,
) -> list[InterventionHandler]:
"""Coerce the ``interventions`` sugar into a list of SDK handlers. ``None``/``"off"`` yield ``[]``."""
if value is None:
return []
values = value if isinstance(value, list) else [value]
handlers = [handler for v in values if (handler := _resolve_one(v, ask)) is not None]
_check_handler_collisions(handlers)
return handlers