feat(migration): phase 2.1-2.3 — sandbox dispatch + thin slice tool wrappers

Phase 2.1 — sandbox dispatch helper:
- strix/tools/_sandbox_dispatch.py: post_to_sandbox() centralizes the
  host->container HTTP wire format. Connect=10s, read=150s timeouts mirror
  legacy executor.py. 50 MB response cap (C18) prevents OOM from a runaway
  tool. All errors surface as {"error": str} so the model can recover
  instead of the run dying.

Phase 2.2 — C6 lock-protected JSONL writes:
- strix/tools/notes/notes_actions.py: notes.jsonl appends are now wrapped
  in _notes_lock so concurrent agents can't interleave half-written lines.
  Regression test in test_notes_jsonl_concurrency.py verifies 1000 parallel
  writes produce exactly 1000 valid JSON lines.

Phase 2.3 — thin-slice SDK wrappers (think + todo + notes):
- strix/tools/_legacy_adapter.py: LegacyAgentStateAdapter shim — exposes
  just enough surface (.agent_id) for legacy tools that close over
  agent_state, sourced from ctx.context['agent_id'].
- strix/tools/thinking/thinking_sdk_tools.py: 1 tool (think).
- strix/tools/todo/todo_sdk_tools.py: 6 tools (create/list/update/done/
  pending/delete) with bulk-form preserved.
- strix/tools/notes/notes_sdk_tools.py: 5 tools (create/list/get/update/
  delete) with asyncio.to_thread around the lock-protected file I/O.

Tests: 22 new tests pass (10 sandbox dispatch + 2 concurrency + 10 SDK
local). Full suite still green.

Per-file ruff ignores added for SDK wrapper files: TC002 (RunContextWrapper
must be runtime-importable because the SDK calls get_type_hints() to
derive the JSON schema) and PLR0911 (sandbox dispatch's 10 short-circuit
returns are intentional, each a distinct documented failure mode).

Refs: PLAYBOOK.md §3.4, AUDIT_R3.md C6/C18.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
0xallam
2026-04-25 00:13:34 -07:00
parent 375389b8bc
commit 6e5d96af34
10 changed files with 1036 additions and 1 deletions
@@ -0,0 +1,32 @@
"""SDK function-tool wrapper for the legacy ``think`` tool.
Pattern: thin async wrapper that delegates to the legacy implementation
in :mod:`strix.tools.thinking.thinking_actions`. The legacy function is
sync and pure (no I/O), so we don't even need ``asyncio.to_thread``.
Validates the simplest tool-port pattern: legacy function in, JSON string
out, no sandbox involvement.
"""
from __future__ import annotations
import json
from strix.tools._decorator import strix_tool
from strix.tools.thinking.thinking_actions import think as _legacy_think
@strix_tool(timeout=10)
async def think(thought: str) -> str:
"""Record a private chain-of-thought note without taking any action.
The "think" tool is the planning escape hatch for situations where a
message-without-tool-call would otherwise halt the run (per the
interactive-mode tool-call requirement). The thought itself is
recorded but produces no side effects.
Args:
thought: The agent's reasoning to record. Must be non-empty.
"""
result = _legacy_think(thought)
return json.dumps(result, ensure_ascii=False)