refactor: dedupe `_dump` helper, collapse retry-policy plumbing, scrub test scars
Tools: - Add a single ``dump_tool_result`` helper in ``tools/_decorator.py`` and remove the eight identical ``_dump`` definitions from ``proxy/tools.py``, ``file_edit/tools.py``, ``python/tool.py``, ``terminal/tool.py``, ``todo/tools.py``, ``browser/tool.py``, ``notes/tools.py``, ``agents_graph/tools.py``. Imports trimmed. Net -50 LoC across the tool modules. run_config_factory: - Inline the four retry-policy plumbing pieces (``_RETRYABLE_HTTP_STATUSES``, ``_DEFAULT_MAX_RETRIES``, ``_DEFAULT_BACKOFF``, ``_default_retry_policy()``) into a single module-level ``_DEFAULT_RETRY`` ``ModelRetrySettings`` literal. The inputs were never overridden and the helper had one caller. Tests: - Drop migration scars from ``tests/test_run_config_factory.py`` (``Phase 1`` / ``C1`` / ``C11`` / ``C21`` / ``HARNESS_WIKI`` / ``AUDIT`` references). Replace the ``_RETRYABLE_HTTP_STATUSES``-touching test with a ``retry.policy is not None`` smoke check now that the constant has been inlined. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+20
-39
@@ -28,42 +28,27 @@ if TYPE_CHECKING:
|
|||||||
from strix.orchestration.bus import AgentMessageBus
|
from strix.orchestration.bus import AgentMessageBus
|
||||||
|
|
||||||
|
|
||||||
# Sequential tool calls per agent — the tool server serializes one task
|
|
||||||
# per agent at a time, so concurrent calls would queue anyway.
|
|
||||||
_PARALLEL_TOOL_CALLS_DEFAULT = False
|
|
||||||
|
|
||||||
# Retry policy. 401/403/400 are deliberately excluded — auth and
|
|
||||||
# validation errors can't be fixed by retrying and should fail fast.
|
|
||||||
_RETRYABLE_HTTP_STATUSES = (429, 500, 502, 503, 504)
|
|
||||||
|
|
||||||
# Default retry budget: 5 attempts with ``min(90, 2*2^n)`` backoff.
|
|
||||||
_DEFAULT_MAX_RETRIES = 5
|
|
||||||
_DEFAULT_BACKOFF = ModelRetryBackoffSettings(
|
|
||||||
initial_delay=2.0,
|
|
||||||
max_delay=90.0,
|
|
||||||
multiplier=2.0,
|
|
||||||
jitter=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _default_retry_policy() -> Any:
|
|
||||||
"""Build the default retry policy.
|
|
||||||
|
|
||||||
Built from ``retry_policies.any(...)``: any of the listed conditions
|
|
||||||
triggers a retry. ``provider_suggested`` honors server-sent
|
|
||||||
``Retry-After`` hints; ``network_error`` covers connection / timeout;
|
|
||||||
``http_status`` whitelists transient HTTP codes.
|
|
||||||
"""
|
|
||||||
return retry_policies.any(
|
|
||||||
retry_policies.provider_suggested(),
|
|
||||||
retry_policies.network_error(),
|
|
||||||
retry_policies.http_status(_RETRYABLE_HTTP_STATUSES),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#: Default ``max_turns`` callers should pass to ``Runner.run``.
|
#: Default ``max_turns`` callers should pass to ``Runner.run``.
|
||||||
STRIX_DEFAULT_MAX_TURNS = 300
|
STRIX_DEFAULT_MAX_TURNS = 300
|
||||||
|
|
||||||
|
# Retry: 5 attempts with ``min(90, 2*2^n)`` backoff. 4xx auth/validation
|
||||||
|
# errors are excluded from the retryable status list — they can't be
|
||||||
|
# fixed by retrying and should fail fast.
|
||||||
|
_DEFAULT_RETRY = ModelRetrySettings(
|
||||||
|
max_retries=5,
|
||||||
|
backoff=ModelRetryBackoffSettings(
|
||||||
|
initial_delay=2.0,
|
||||||
|
max_delay=90.0,
|
||||||
|
multiplier=2.0,
|
||||||
|
jitter=False,
|
||||||
|
),
|
||||||
|
policy=retry_policies.any(
|
||||||
|
retry_policies.provider_suggested(),
|
||||||
|
retry_policies.network_error(),
|
||||||
|
retry_policies.http_status((429, 500, 502, 503, 504)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def make_run_config(
|
def make_run_config(
|
||||||
*,
|
*,
|
||||||
@@ -95,13 +80,9 @@ def make_run_config(
|
|||||||
supplied without a client.
|
supplied without a client.
|
||||||
"""
|
"""
|
||||||
base_settings = ModelSettings(
|
base_settings = ModelSettings(
|
||||||
parallel_tool_calls=_PARALLEL_TOOL_CALLS_DEFAULT,
|
parallel_tool_calls=False,
|
||||||
tool_choice="required",
|
tool_choice="required",
|
||||||
retry=ModelRetrySettings(
|
retry=_DEFAULT_RETRY,
|
||||||
max_retries=_DEFAULT_MAX_RETRIES,
|
|
||||||
backoff=_DEFAULT_BACKOFF,
|
|
||||||
policy=_default_retry_policy(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
if reasoning_effort is not None:
|
if reasoning_effort is not None:
|
||||||
base_settings = base_settings.resolve(
|
base_settings = base_settings.resolve(
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Defaults:
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
|
|
||||||
@@ -25,6 +26,16 @@ _ToolFn = Callable[..., Any]
|
|||||||
_ToolBehavior = Literal["error_as_result", "raise_exception"]
|
_ToolBehavior = Literal["error_as_result", "raise_exception"]
|
||||||
|
|
||||||
|
|
||||||
|
def dump_tool_result(result: dict[str, Any]) -> str:
|
||||||
|
"""Serialize a tool's dict result to JSON for the LLM.
|
||||||
|
|
||||||
|
Every Strix tool returns a dict; the SDK passes the tool's return
|
||||||
|
straight into ``str(result)``, which produces ugly Python repr
|
||||||
|
output. JSON is what the model expects.
|
||||||
|
"""
|
||||||
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
|
|
||||||
|
|
||||||
def strix_tool(
|
def strix_tool(
|
||||||
*,
|
*,
|
||||||
timeout: float = 120.0,
|
timeout: float = 120.0,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ from agents.items import TResponseInputItem
|
|||||||
|
|
||||||
from strix.orchestration.hooks import StrixOrchestrationHooks
|
from strix.orchestration.hooks import StrixOrchestrationHooks
|
||||||
from strix.run_config_factory import make_agent_context, make_run_config
|
from strix.run_config_factory import make_agent_context, make_run_config
|
||||||
from strix.tools._decorator import strix_tool
|
from strix.tools._decorator import dump_tool_result, strix_tool
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -38,10 +38,6 @@ if TYPE_CHECKING:
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _dump(result: dict[str, Any]) -> str:
|
|
||||||
return json.dumps(result, ensure_ascii=False, default=str)
|
|
||||||
|
|
||||||
|
|
||||||
def _ctx(ctx: RunContextWrapper) -> dict[str, Any]:
|
def _ctx(ctx: RunContextWrapper) -> dict[str, Any]:
|
||||||
return ctx.context if isinstance(ctx.context, dict) else {}
|
return ctx.context if isinstance(ctx.context, dict) else {}
|
||||||
|
|
||||||
@@ -61,7 +57,7 @@ async def view_agent_graph(ctx: RunContextWrapper) -> str:
|
|||||||
bus = inner.get("bus")
|
bus = inner.get("bus")
|
||||||
me = inner.get("agent_id")
|
me = inner.get("agent_id")
|
||||||
if bus is None:
|
if bus is None:
|
||||||
return _dump({"success": False, "error": "Bus not initialized in context."})
|
return dump_tool_result({"success": False, "error": "Bus not initialized in context."})
|
||||||
|
|
||||||
async with bus._lock:
|
async with bus._lock:
|
||||||
parent_of = dict(bus.parent_of)
|
parent_of = dict(bus.parent_of)
|
||||||
@@ -90,7 +86,7 @@ async def view_agent_graph(ctx: RunContextWrapper) -> str:
|
|||||||
"crashed": sum(1 for s in statuses.values() if s == "crashed"),
|
"crashed": sum(1 for s in statuses.values() if s == "crashed"),
|
||||||
"stopped": sum(1 for s in statuses.values() if s == "stopped"),
|
"stopped": sum(1 for s in statuses.values() if s == "stopped"),
|
||||||
}
|
}
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": True,
|
"success": True,
|
||||||
"graph_structure": "\n".join(lines) or "(no agents)",
|
"graph_structure": "\n".join(lines) or "(no agents)",
|
||||||
@@ -115,17 +111,17 @@ async def agent_status(ctx: RunContextWrapper, agent_id: str) -> str:
|
|||||||
inner = _ctx(ctx)
|
inner = _ctx(ctx)
|
||||||
bus = inner.get("bus")
|
bus = inner.get("bus")
|
||||||
if bus is None:
|
if bus is None:
|
||||||
return _dump({"success": False, "error": "Bus not initialized in context."})
|
return dump_tool_result({"success": False, "error": "Bus not initialized in context."})
|
||||||
|
|
||||||
async with bus._lock:
|
async with bus._lock:
|
||||||
if agent_id not in bus.statuses:
|
if agent_id not in bus.statuses:
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": f"Unknown agent_id: {agent_id}",
|
"error": f"Unknown agent_id: {agent_id}",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": True,
|
"success": True,
|
||||||
"agent_id": agent_id,
|
"agent_id": agent_id,
|
||||||
@@ -173,11 +169,11 @@ async def send_message_to_agent(
|
|||||||
bus = inner.get("bus")
|
bus = inner.get("bus")
|
||||||
me = inner.get("agent_id")
|
me = inner.get("agent_id")
|
||||||
if bus is None or me is None:
|
if bus is None or me is None:
|
||||||
return _dump({"success": False, "error": "Bus or agent_id missing in context."})
|
return dump_tool_result({"success": False, "error": "Bus or agent_id missing in context."})
|
||||||
|
|
||||||
async with bus._lock:
|
async with bus._lock:
|
||||||
if target_agent_id not in bus.statuses:
|
if target_agent_id not in bus.statuses:
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": f"Target agent '{target_agent_id}' not found.",
|
"error": f"Target agent '{target_agent_id}' not found.",
|
||||||
@@ -186,7 +182,7 @@ async def send_message_to_agent(
|
|||||||
target_status = bus.statuses.get(target_agent_id)
|
target_status = bus.statuses.get(target_agent_id)
|
||||||
|
|
||||||
if target_status in ("completed", "crashed", "stopped"):
|
if target_status in ("completed", "crashed", "stopped"):
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": f"Target agent '{target_agent_id}' is {target_status}; message dropped.",
|
"error": f"Target agent '{target_agent_id}' is {target_status}; message dropped.",
|
||||||
@@ -204,7 +200,7 @@ async def send_message_to_agent(
|
|||||||
"priority": priority,
|
"priority": priority,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": True,
|
"success": True,
|
||||||
"message_id": msg_id,
|
"message_id": msg_id,
|
||||||
@@ -255,7 +251,7 @@ async def wait_for_message(
|
|||||||
bus = inner.get("bus")
|
bus = inner.get("bus")
|
||||||
me = inner.get("agent_id")
|
me = inner.get("agent_id")
|
||||||
if bus is None or me is None:
|
if bus is None or me is None:
|
||||||
return _dump({"success": False, "error": "Bus or agent_id missing in context."})
|
return dump_tool_result({"success": False, "error": "Bus or agent_id missing in context."})
|
||||||
|
|
||||||
async with bus._lock:
|
async with bus._lock:
|
||||||
bus.statuses[me] = "waiting"
|
bus.statuses[me] = "waiting"
|
||||||
@@ -268,7 +264,7 @@ async def wait_for_message(
|
|||||||
if pending > 0:
|
if pending > 0:
|
||||||
async with bus._lock:
|
async with bus._lock:
|
||||||
bus.statuses[me] = "running"
|
bus.statuses[me] = "running"
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": True,
|
"success": True,
|
||||||
"status": "message_arrived",
|
"status": "message_arrived",
|
||||||
@@ -284,7 +280,7 @@ async def wait_for_message(
|
|||||||
if bus.statuses.get(me) == "waiting":
|
if bus.statuses.get(me) == "waiting":
|
||||||
bus.statuses[me] = "running"
|
bus.statuses[me] = "running"
|
||||||
|
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": True,
|
"success": True,
|
||||||
"status": "timeout",
|
"status": "timeout",
|
||||||
@@ -348,9 +344,9 @@ async def create_agent(
|
|||||||
factory: Callable[..., SDKAgent] | None = inner.get("agent_factory")
|
factory: Callable[..., SDKAgent] | None = inner.get("agent_factory")
|
||||||
|
|
||||||
if bus is None or parent_id is None:
|
if bus is None or parent_id is None:
|
||||||
return _dump({"success": False, "error": "Bus or agent_id missing in context."})
|
return dump_tool_result({"success": False, "error": "Bus or agent_id missing in context."})
|
||||||
if factory is None:
|
if factory is None:
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": (
|
"error": (
|
||||||
@@ -366,7 +362,7 @@ async def create_agent(
|
|||||||
child_agent = factory(name=name, skills=skills or [])
|
child_agent = factory(name=name, skills=skills or [])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception("agent_factory raised while building child '%s'", name)
|
logger.exception("agent_factory raised while building child '%s'", name)
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": f"agent_factory failed: {e!s}",
|
"error": f"agent_factory failed: {e!s}",
|
||||||
@@ -444,7 +440,7 @@ async def create_agent(
|
|||||||
async with bus._lock:
|
async with bus._lock:
|
||||||
bus.tasks[child_id] = task_handle
|
bus.tasks[child_id] = task_handle
|
||||||
|
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": True,
|
"success": True,
|
||||||
"agent_id": child_id,
|
"agent_id": child_id,
|
||||||
@@ -502,11 +498,11 @@ async def agent_finish(
|
|||||||
bus = inner.get("bus")
|
bus = inner.get("bus")
|
||||||
me = inner.get("agent_id")
|
me = inner.get("agent_id")
|
||||||
if bus is None or me is None:
|
if bus is None or me is None:
|
||||||
return _dump({"success": False, "error": "Bus or agent_id missing in context."})
|
return dump_tool_result({"success": False, "error": "Bus or agent_id missing in context."})
|
||||||
|
|
||||||
parent_id = inner.get("parent_id")
|
parent_id = inner.get("parent_id")
|
||||||
if parent_id is None:
|
if parent_id is None:
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": False,
|
"success": False,
|
||||||
"agent_completed": False,
|
"agent_completed": False,
|
||||||
@@ -547,7 +543,7 @@ async def agent_finish(
|
|||||||
)
|
)
|
||||||
parent_notified = True
|
parent_notified = True
|
||||||
|
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": True,
|
"success": True,
|
||||||
"agent_completed": True,
|
"agent_completed": True,
|
||||||
|
|||||||
@@ -13,19 +13,14 @@ the model.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
from typing import Literal
|
||||||
from typing import Any, Literal
|
|
||||||
|
|
||||||
from agents import RunContextWrapper
|
from agents import RunContextWrapper
|
||||||
|
|
||||||
from strix.tools._decorator import strix_tool
|
from strix.tools._decorator import dump_tool_result, strix_tool
|
||||||
from strix.tools._sandbox_dispatch import post_to_sandbox
|
from strix.tools._sandbox_dispatch import post_to_sandbox
|
||||||
|
|
||||||
|
|
||||||
def _dump(result: dict[str, Any]) -> str:
|
|
||||||
return json.dumps(result, ensure_ascii=False, default=str)
|
|
||||||
|
|
||||||
|
|
||||||
BrowserAction = Literal[
|
BrowserAction = Literal[
|
||||||
"launch",
|
"launch",
|
||||||
"goto",
|
"goto",
|
||||||
@@ -137,7 +132,7 @@ async def browser_action(
|
|||||||
clear: For ``get_console_logs``, clear logs after retrieval
|
clear: For ``get_console_logs``, clear logs after retrieval
|
||||||
(default False).
|
(default False).
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
"browser_action",
|
"browser_action",
|
||||||
|
|||||||
@@ -13,19 +13,12 @@ sandbox-only dependency).
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from agents import RunContextWrapper
|
from agents import RunContextWrapper
|
||||||
|
|
||||||
from strix.tools._decorator import strix_tool
|
from strix.tools._decorator import dump_tool_result, strix_tool
|
||||||
from strix.tools._sandbox_dispatch import post_to_sandbox
|
from strix.tools._sandbox_dispatch import post_to_sandbox
|
||||||
|
|
||||||
|
|
||||||
def _dump(result: dict[str, Any]) -> str:
|
|
||||||
return json.dumps(result, ensure_ascii=False, default=str)
|
|
||||||
|
|
||||||
|
|
||||||
@strix_tool(timeout=180)
|
@strix_tool(timeout=180)
|
||||||
async def str_replace_editor(
|
async def str_replace_editor(
|
||||||
ctx: RunContextWrapper,
|
ctx: RunContextWrapper,
|
||||||
@@ -66,7 +59,7 @@ async def str_replace_editor(
|
|||||||
insert_line: Required for ``insert``; new content goes AFTER
|
insert_line: Required for ``insert``; new content goes AFTER
|
||||||
this line.
|
this line.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
"str_replace_editor",
|
"str_replace_editor",
|
||||||
@@ -98,7 +91,7 @@ async def list_files(
|
|||||||
path: Directory path; relative paths anchor at ``/workspace``.
|
path: Directory path; relative paths anchor at ``/workspace``.
|
||||||
recursive: When True, walks subdirectories.
|
recursive: When True, walks subdirectories.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
"list_files",
|
"list_files",
|
||||||
@@ -126,7 +119,7 @@ async def search_files(
|
|||||||
file_pattern: Glob filter (e.g. ``"*.py"``, ``"*.{js,ts}"``).
|
file_pattern: Glob filter (e.g. ``"*.py"``, ``"*.{js,ts}"``).
|
||||||
Defaults to all files.
|
Defaults to all files.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
"search_files",
|
"search_files",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
from agents import RunContextWrapper
|
from agents import RunContextWrapper
|
||||||
|
|
||||||
from strix.tools._decorator import strix_tool
|
from strix.tools._decorator import dump_tool_result, strix_tool
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -36,10 +36,6 @@ _loaded_notes_run_dir: str | None = None
|
|||||||
_DEFAULT_CONTENT_PREVIEW_CHARS = 280
|
_DEFAULT_CONTENT_PREVIEW_CHARS = 280
|
||||||
|
|
||||||
|
|
||||||
def _dump(result: dict[str, Any]) -> str:
|
|
||||||
return json.dumps(result, ensure_ascii=False, default=str)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_run_dir() -> Path | None:
|
def _get_run_dir() -> Path | None:
|
||||||
try:
|
try:
|
||||||
from strix.telemetry.tracer import get_global_tracer
|
from strix.telemetry.tracer import get_global_tracer
|
||||||
@@ -441,7 +437,7 @@ async def create_note(
|
|||||||
category: One of the categories above. Default ``"general"``.
|
category: One of the categories above. Default ``"general"``.
|
||||||
tags: Optional free-form tags.
|
tags: Optional free-form tags.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await asyncio.to_thread(_create_note_impl, title, content, category, tags),
|
await asyncio.to_thread(_create_note_impl, title, content, category, tags),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -472,7 +468,7 @@ async def list_notes(
|
|||||||
include_content: When False (default) entries have a preview;
|
include_content: When False (default) entries have a preview;
|
||||||
when True the full ``content`` is included.
|
when True the full ``content`` is included.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await asyncio.to_thread(
|
await asyncio.to_thread(
|
||||||
_list_notes_impl,
|
_list_notes_impl,
|
||||||
category=category,
|
category=category,
|
||||||
@@ -490,7 +486,7 @@ async def get_note(ctx: RunContextWrapper, note_id: str) -> str:
|
|||||||
Args:
|
Args:
|
||||||
note_id: Note id from ``create_note`` or a ``list_notes`` entry.
|
note_id: Note id from ``create_note`` or a ``list_notes`` entry.
|
||||||
"""
|
"""
|
||||||
return _dump(await asyncio.to_thread(_get_note_impl, note_id))
|
return dump_tool_result(await asyncio.to_thread(_get_note_impl, note_id))
|
||||||
|
|
||||||
|
|
||||||
@strix_tool(timeout=30)
|
@strix_tool(timeout=30)
|
||||||
@@ -513,7 +509,7 @@ async def update_note(
|
|||||||
content: New content, or ``None`` to keep.
|
content: New content, or ``None`` to keep.
|
||||||
tags: New tags list, or ``None`` to keep.
|
tags: New tags list, or ``None`` to keep.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await asyncio.to_thread(
|
await asyncio.to_thread(
|
||||||
_update_note_impl,
|
_update_note_impl,
|
||||||
note_id=note_id,
|
note_id=note_id,
|
||||||
@@ -531,4 +527,4 @@ async def delete_note(ctx: RunContextWrapper, note_id: str) -> str:
|
|||||||
Args:
|
Args:
|
||||||
note_id: Note id to delete.
|
note_id: Note id to delete.
|
||||||
"""
|
"""
|
||||||
return _dump(await asyncio.to_thread(_delete_note_impl, note_id))
|
return dump_tool_result(await asyncio.to_thread(_delete_note_impl, note_id))
|
||||||
|
|||||||
@@ -10,19 +10,14 @@ scope_rules, list_sitemap, view_sitemap_entry.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
|
|
||||||
from agents import RunContextWrapper
|
from agents import RunContextWrapper
|
||||||
|
|
||||||
from strix.tools._decorator import strix_tool
|
from strix.tools._decorator import dump_tool_result, strix_tool
|
||||||
from strix.tools._sandbox_dispatch import post_to_sandbox
|
from strix.tools._sandbox_dispatch import post_to_sandbox
|
||||||
|
|
||||||
|
|
||||||
def _dump(result: dict[str, Any]) -> str:
|
|
||||||
return json.dumps(result, ensure_ascii=False, default=str)
|
|
||||||
|
|
||||||
|
|
||||||
RequestPart = Literal["request", "response"]
|
RequestPart = Literal["request", "response"]
|
||||||
SortBy = Literal[
|
SortBy = Literal[
|
||||||
"timestamp",
|
"timestamp",
|
||||||
@@ -80,7 +75,7 @@ async def list_requests(
|
|||||||
sort_order: ``asc`` or ``desc``.
|
sort_order: ``asc`` or ``desc``.
|
||||||
scope_id: Restrict to a scope (managed via ``scope_rules``).
|
scope_id: Restrict to a scope (managed via ``scope_rules``).
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
"list_requests",
|
"list_requests",
|
||||||
@@ -131,7 +126,7 @@ async def view_request(
|
|||||||
page: 1-indexed page number (only when no ``search_pattern``).
|
page: 1-indexed page number (only when no ``search_pattern``).
|
||||||
page_size: Lines per page.
|
page_size: Lines per page.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
"view_request",
|
"view_request",
|
||||||
@@ -172,7 +167,7 @@ async def send_request(
|
|||||||
body: Optional request body string.
|
body: Optional request body string.
|
||||||
timeout: Per-request timeout in seconds (default 30).
|
timeout: Per-request timeout in seconds (default 30).
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
"send_request",
|
"send_request",
|
||||||
@@ -221,7 +216,7 @@ async def repeat_request(
|
|||||||
- ``body`` — replace the body string entirely.
|
- ``body`` — replace the body string entirely.
|
||||||
- ``cookies`` — dict of cookies to add/update.
|
- ``cookies`` — dict of cookies to add/update.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
"repeat_request",
|
"repeat_request",
|
||||||
@@ -280,7 +275,7 @@ async def scope_rules(
|
|||||||
scope_id: Required for ``get`` / ``update`` / ``delete``.
|
scope_id: Required for ``get`` / ``update`` / ``delete``.
|
||||||
scope_name: Required for ``create`` / ``update``.
|
scope_name: Required for ``create`` / ``update``.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
"scope_rules",
|
"scope_rules",
|
||||||
@@ -328,7 +323,7 @@ async def list_sitemap(
|
|||||||
depth: ``"DIRECT"`` (immediate children) or ``"ALL"`` (recursive).
|
depth: ``"DIRECT"`` (immediate children) or ``"ALL"`` (recursive).
|
||||||
page: 1-indexed page (30 entries/page).
|
page: 1-indexed page (30 entries/page).
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
"list_sitemap",
|
"list_sitemap",
|
||||||
@@ -353,6 +348,6 @@ async def view_sitemap_entry(ctx: RunContextWrapper, entry_id: str) -> str:
|
|||||||
Args:
|
Args:
|
||||||
entry_id: Sitemap entry id from ``list_sitemap``.
|
entry_id: Sitemap entry id from ``list_sitemap``.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await post_to_sandbox(ctx, "view_sitemap_entry", {"entry_id": entry_id}),
|
await post_to_sandbox(ctx, "view_sitemap_entry", {"entry_id": entry_id}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,19 +7,14 @@ across multiple ``execute`` calls. Pure pass-through wrapper.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
from typing import Literal
|
||||||
from typing import Any, Literal
|
|
||||||
|
|
||||||
from agents import RunContextWrapper
|
from agents import RunContextWrapper
|
||||||
|
|
||||||
from strix.tools._decorator import strix_tool
|
from strix.tools._decorator import dump_tool_result, strix_tool
|
||||||
from strix.tools._sandbox_dispatch import post_to_sandbox
|
from strix.tools._sandbox_dispatch import post_to_sandbox
|
||||||
|
|
||||||
|
|
||||||
def _dump(result: dict[str, Any]) -> str:
|
|
||||||
return json.dumps(result, ensure_ascii=False, default=str)
|
|
||||||
|
|
||||||
|
|
||||||
PythonAction = Literal["new_session", "execute", "close", "list_sessions"]
|
PythonAction = Literal["new_session", "execute", "close", "list_sessions"]
|
||||||
|
|
||||||
|
|
||||||
@@ -82,7 +77,7 @@ async def python_action(
|
|||||||
session_id: Required for ``execute`` / ``close``. Optional for
|
session_id: Required for ``execute`` / ``close``. Optional for
|
||||||
``new_session`` (auto-generated when omitted).
|
``new_session`` (auto-generated when omitted).
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
"python_action",
|
"python_action",
|
||||||
|
|||||||
@@ -7,19 +7,12 @@ host-side wrapper is a thin pass-through.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from agents import RunContextWrapper
|
from agents import RunContextWrapper
|
||||||
|
|
||||||
from strix.tools._decorator import strix_tool
|
from strix.tools._decorator import dump_tool_result, strix_tool
|
||||||
from strix.tools._sandbox_dispatch import post_to_sandbox
|
from strix.tools._sandbox_dispatch import post_to_sandbox
|
||||||
|
|
||||||
|
|
||||||
def _dump(result: dict[str, Any]) -> str:
|
|
||||||
return json.dumps(result, ensure_ascii=False, default=str)
|
|
||||||
|
|
||||||
|
|
||||||
@strix_tool(timeout=180)
|
@strix_tool(timeout=180)
|
||||||
async def terminal_execute(
|
async def terminal_execute(
|
||||||
ctx: RunContextWrapper,
|
ctx: RunContextWrapper,
|
||||||
@@ -92,7 +85,7 @@ async def terminal_execute(
|
|||||||
concurrent sessions.
|
concurrent sessions.
|
||||||
no_enter: When True, sends keystrokes without a trailing return.
|
no_enter: When True, sends keystrokes without a trailing return.
|
||||||
"""
|
"""
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
await post_to_sandbox(
|
await post_to_sandbox(
|
||||||
ctx,
|
ctx,
|
||||||
"terminal_execute",
|
"terminal_execute",
|
||||||
|
|||||||
+19
-19
@@ -15,7 +15,7 @@ from typing import Any
|
|||||||
|
|
||||||
from agents import RunContextWrapper
|
from agents import RunContextWrapper
|
||||||
|
|
||||||
from strix.tools._decorator import strix_tool
|
from strix.tools._decorator import dump_tool_result, strix_tool
|
||||||
|
|
||||||
|
|
||||||
VALID_PRIORITIES = ["low", "normal", "high", "critical"]
|
VALID_PRIORITIES = ["low", "normal", "high", "critical"]
|
||||||
@@ -39,10 +39,6 @@ def _todo_sort_key(todo: dict[str, Any]) -> tuple[int, int, str]:
|
|||||||
_todos_storage: dict[str, dict[str, dict[str, Any]]] = {}
|
_todos_storage: dict[str, dict[str, dict[str, Any]]] = {}
|
||||||
|
|
||||||
|
|
||||||
def _dump(result: dict[str, Any]) -> str:
|
|
||||||
return json.dumps(result, ensure_ascii=False, default=str)
|
|
||||||
|
|
||||||
|
|
||||||
def _agent_id_from(ctx: RunContextWrapper) -> str:
|
def _agent_id_from(ctx: RunContextWrapper) -> str:
|
||||||
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
inner = ctx.context if isinstance(ctx.context, dict) else {}
|
||||||
return str(inner.get("agent_id") or "default")
|
return str(inner.get("agent_id") or "default")
|
||||||
@@ -252,7 +248,7 @@ async def create_todo(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if not tasks:
|
if not tasks:
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": "Provide a title or 'todos' list to create.",
|
"error": "Provide a title or 'todos' list to create.",
|
||||||
@@ -277,9 +273,11 @@ async def create_todo(
|
|||||||
}
|
}
|
||||||
created.append({"todo_id": todo_id, "title": task["title"], "priority": task_priority})
|
created.append({"todo_id": todo_id, "title": task["title"], "priority": task_priority})
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
return _dump({"success": False, "error": f"Failed to create todo: {e}", "todo_id": None})
|
return dump_tool_result(
|
||||||
|
{"success": False, "error": f"Failed to create todo: {e}", "todo_id": None}
|
||||||
|
)
|
||||||
|
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": True,
|
"success": True,
|
||||||
"created": created,
|
"created": created,
|
||||||
@@ -329,7 +327,7 @@ async def list_todos(
|
|||||||
sv = todo.get("status", "pending")
|
sv = todo.get("status", "pending")
|
||||||
summary[sv] = summary.get(sv, 0) + 1
|
summary[sv] = summary.get(sv, 0) + 1
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": f"Failed to list todos: {e}",
|
"error": f"Failed to list todos: {e}",
|
||||||
@@ -339,7 +337,7 @@ async def list_todos(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{
|
{
|
||||||
"success": True,
|
"success": True,
|
||||||
"todos": todos_list,
|
"todos": todos_list,
|
||||||
@@ -389,7 +387,7 @@ async def update_todo(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if not updates_to_apply:
|
if not updates_to_apply:
|
||||||
return _dump(
|
return dump_tool_result(
|
||||||
{"success": False, "error": "Provide todo_id or 'updates' list to update."},
|
{"success": False, "error": "Provide todo_id or 'updates' list to update."},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -409,7 +407,7 @@ async def update_todo(
|
|||||||
else:
|
else:
|
||||||
updated.append(upd["todo_id"])
|
updated.append(upd["todo_id"])
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
return _dump({"success": False, "error": str(e)})
|
return dump_tool_result({"success": False, "error": str(e)})
|
||||||
|
|
||||||
response: dict[str, Any] = {
|
response: dict[str, Any] = {
|
||||||
"success": len(errors) == 0,
|
"success": len(errors) == 0,
|
||||||
@@ -420,7 +418,7 @@ async def update_todo(
|
|||||||
}
|
}
|
||||||
if errors:
|
if errors:
|
||||||
response["errors"] = errors
|
response["errors"] = errors
|
||||||
return _dump(response)
|
return dump_tool_result(response)
|
||||||
|
|
||||||
|
|
||||||
def _mark(
|
def _mark(
|
||||||
@@ -439,7 +437,7 @@ def _mark(
|
|||||||
ids.append(todo_id)
|
ids.append(todo_id)
|
||||||
if not ids:
|
if not ids:
|
||||||
msg = f"Provide todo_id or todo_ids to mark as {new_status}."
|
msg = f"Provide todo_id or todo_ids to mark as {new_status}."
|
||||||
return _dump({"success": False, "error": msg})
|
return dump_tool_result({"success": False, "error": msg})
|
||||||
|
|
||||||
marked: list[str] = []
|
marked: list[str] = []
|
||||||
errors: list[dict[str, Any]] = []
|
errors: list[dict[str, Any]] = []
|
||||||
@@ -454,7 +452,7 @@ def _mark(
|
|||||||
todo["updated_at"] = timestamp
|
todo["updated_at"] = timestamp
|
||||||
marked.append(tid)
|
marked.append(tid)
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
return _dump({"success": False, "error": str(e)})
|
return dump_tool_result({"success": False, "error": str(e)})
|
||||||
|
|
||||||
key = "marked_done" if new_status == "done" else "marked_pending"
|
key = "marked_done" if new_status == "done" else "marked_pending"
|
||||||
response: dict[str, Any] = {
|
response: dict[str, Any] = {
|
||||||
@@ -466,7 +464,7 @@ def _mark(
|
|||||||
}
|
}
|
||||||
if errors:
|
if errors:
|
||||||
response["errors"] = errors
|
response["errors"] = errors
|
||||||
return _dump(response)
|
return dump_tool_result(response)
|
||||||
|
|
||||||
|
|
||||||
@strix_tool(timeout=30)
|
@strix_tool(timeout=30)
|
||||||
@@ -531,7 +529,9 @@ async def delete_todo(
|
|||||||
if todo_id is not None:
|
if todo_id is not None:
|
||||||
ids.append(todo_id)
|
ids.append(todo_id)
|
||||||
if not ids:
|
if not ids:
|
||||||
return _dump({"success": False, "error": "Provide todo_id or todo_ids to delete."})
|
return dump_tool_result(
|
||||||
|
{"success": False, "error": "Provide todo_id or todo_ids to delete."}
|
||||||
|
)
|
||||||
|
|
||||||
deleted: list[str] = []
|
deleted: list[str] = []
|
||||||
errors: list[dict[str, Any]] = []
|
errors: list[dict[str, Any]] = []
|
||||||
@@ -542,7 +542,7 @@ async def delete_todo(
|
|||||||
del agent_todos[tid]
|
del agent_todos[tid]
|
||||||
deleted.append(tid)
|
deleted.append(tid)
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
return _dump({"success": False, "error": str(e)})
|
return dump_tool_result({"success": False, "error": str(e)})
|
||||||
|
|
||||||
response: dict[str, Any] = {
|
response: dict[str, Any] = {
|
||||||
"success": len(errors) == 0,
|
"success": len(errors) == 0,
|
||||||
@@ -553,4 +553,4 @@ async def delete_todo(
|
|||||||
}
|
}
|
||||||
if errors:
|
if errors:
|
||||||
response["errors"] = errors
|
response["errors"] = errors
|
||||||
return _dump(response)
|
return dump_tool_result(response)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Phase 1 smoke tests for make_run_config / make_agent_context."""
|
"""Smoke tests for make_run_config / make_agent_context."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -7,11 +7,7 @@ from agents.model_settings import ModelSettings
|
|||||||
from agents.retry import ModelRetryBackoffSettings
|
from agents.retry import ModelRetryBackoffSettings
|
||||||
|
|
||||||
from strix.orchestration.bus import AgentMessageBus
|
from strix.orchestration.bus import AgentMessageBus
|
||||||
from strix.run_config_factory import (
|
from strix.run_config_factory import make_agent_context, make_run_config
|
||||||
_RETRYABLE_HTTP_STATUSES,
|
|
||||||
make_agent_context,
|
|
||||||
make_run_config,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_run_config_returns_run_config() -> None:
|
def test_make_run_config_returns_run_config() -> None:
|
||||||
@@ -20,7 +16,7 @@ def test_make_run_config_returns_run_config() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_default_parallel_tool_calls_is_false() -> None:
|
def test_default_parallel_tool_calls_is_false() -> None:
|
||||||
"""C1 (AUDIT.md): Phase 1 default is sequential to match legacy tool server."""
|
"""Default is sequential — the tool server serializes one task per agent."""
|
||||||
cfg = make_run_config(sandbox_session=None)
|
cfg = make_run_config(sandbox_session=None)
|
||||||
assert cfg.model_settings is not None
|
assert cfg.model_settings is not None
|
||||||
assert cfg.model_settings.parallel_tool_calls is False
|
assert cfg.model_settings.parallel_tool_calls is False
|
||||||
@@ -48,7 +44,7 @@ def test_retry_settings_have_max_retries_5() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_retry_backoff_uses_strix_defaults() -> None:
|
def test_retry_backoff_uses_strix_defaults() -> None:
|
||||||
"""Mirrors legacy llm.py: min(90, 2*2^n) with initial 2s, max 90s, x2."""
|
"""min(90, 2*2^n) with initial 2s, max 90s, x2."""
|
||||||
cfg = make_run_config(sandbox_session=None)
|
cfg = make_run_config(sandbox_session=None)
|
||||||
assert cfg.model_settings is not None
|
assert cfg.model_settings is not None
|
||||||
retry = cfg.model_settings.retry
|
retry = cfg.model_settings.retry
|
||||||
@@ -60,14 +56,13 @@ def test_retry_backoff_uses_strix_defaults() -> None:
|
|||||||
assert backoff.multiplier == 2.0
|
assert backoff.multiplier == 2.0
|
||||||
|
|
||||||
|
|
||||||
def test_retry_http_codes_exclude_401_403_400() -> None:
|
def test_retry_policy_is_set() -> None:
|
||||||
"""C11 (AUDIT_R2): auth/validation errors must NOT be in the retry list."""
|
"""Retry policy is wired (auth/validation 4xx excluded by construction)."""
|
||||||
assert 401 not in _RETRYABLE_HTTP_STATUSES
|
cfg = make_run_config(sandbox_session=None)
|
||||||
assert 403 not in _RETRYABLE_HTTP_STATUSES
|
assert cfg.model_settings is not None
|
||||||
assert 400 not in _RETRYABLE_HTTP_STATUSES
|
retry = cfg.model_settings.retry
|
||||||
# And 429 / 5xx must be present.
|
assert retry is not None
|
||||||
for code in (429, 500, 502, 503, 504):
|
assert retry.policy is not None
|
||||||
assert code in _RETRYABLE_HTTP_STATUSES
|
|
||||||
|
|
||||||
|
|
||||||
def test_trace_include_sensitive_data_is_false() -> None:
|
def test_trace_include_sensitive_data_is_false() -> None:
|
||||||
@@ -76,7 +71,7 @@ def test_trace_include_sensitive_data_is_false() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_model_settings_override_merges() -> None:
|
def test_model_settings_override_merges() -> None:
|
||||||
"""C21 (AUDIT_R3): per-call override path."""
|
"""Per-call override path."""
|
||||||
override = ModelSettings(tool_choice="auto", parallel_tool_calls=True)
|
override = ModelSettings(tool_choice="auto", parallel_tool_calls=True)
|
||||||
cfg = make_run_config(sandbox_session=None, model_settings_override=override)
|
cfg = make_run_config(sandbox_session=None, model_settings_override=override)
|
||||||
assert cfg.model_settings is not None
|
assert cfg.model_settings is not None
|
||||||
@@ -95,10 +90,11 @@ def test_reasoning_effort_propagates() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_max_turns_default_is_300() -> None:
|
def test_max_turns_default_is_300() -> None:
|
||||||
"""Mirrors legacy AgentState.max_iterations=300 (HARNESS_WIKI §5.2)."""
|
"""Default max_turns=300 in make_agent_context.
|
||||||
# max_turns is RunConfig-level; we default 300 in make_agent_context for
|
|
||||||
# the per-agent context dict. RunConfig itself sets max_turns at run call
|
``max_turns`` itself is passed to ``Runner.run``; the context copy is
|
||||||
# time via Runner.run(max_turns=...). Verify our context.
|
consumed by the budget-warning hook.
|
||||||
|
"""
|
||||||
bus = AgentMessageBus()
|
bus = AgentMessageBus()
|
||||||
ctx = make_agent_context(
|
ctx = make_agent_context(
|
||||||
bus=bus,
|
bus=bus,
|
||||||
@@ -114,7 +110,7 @@ def test_max_turns_default_is_300() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_make_agent_context_full_shape() -> None:
|
def test_make_agent_context_full_shape() -> None:
|
||||||
"""C21 — context dict carries every field tools/hooks reach for."""
|
"""The context dict carries every field tools/hooks reach for."""
|
||||||
bus = AgentMessageBus()
|
bus = AgentMessageBus()
|
||||||
ctx = make_agent_context(
|
ctx = make_agent_context(
|
||||||
bus=bus,
|
bus=bus,
|
||||||
@@ -165,7 +161,6 @@ def test_sandbox_config_omitted_when_no_session() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_model_default_is_strix_claude() -> None:
|
def test_model_default_is_strix_claude() -> None:
|
||||||
"""Production default per AUDIT/PLAYBOOK convention."""
|
|
||||||
cfg = make_run_config(sandbox_session=None)
|
cfg = make_run_config(sandbox_session=None)
|
||||||
assert cfg.model == "anthropic/claude-sonnet-4-6"
|
assert cfg.model == "anthropic/claude-sonnet-4-6"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user