refactor: inline non-sandbox actions, strip registry, drop schemas

Cleanup pass after the migration:

#1 Inline ``*_actions.py`` into wrapper ``tool[s].py`` for the
non-sandbox tools (think, todo, notes, reporting, web_search,
finish_scan). One file per tool family now. Helpers + public
function bodies live alongside the ``@strix_tool``-decorated
wrappers that call them.

For notes, the sync helpers are renamed to ``_create_note_impl`` /
``_list_notes_impl`` / etc. so the public names ``create_note`` /
``list_notes`` / etc. can be the FunctionTool instances the agent
factory imports. ``append_note_content`` (used by the agents-graph
wiki-update hook) calls the impl helpers directly.

#2 Delete ``strix/tools/_state_adapter.py``. The ``AgentStateAdapter``
shim only existed to feed legacy ``*_actions.py`` functions a
``state.agent_id`` they could read. With the actions inlined, the
wrappers read ``ctx.context['agent_id']`` directly.

#3 Strip ``strix/tools/registry.py`` from ~250 LOC to ~110.
Deleted: XML schema loading, ``_parse_param_schema``,
``get_tools_prompt``, ``get_tool_param_schema``, ``needs_agent_state``,
``should_execute_in_sandbox``, ``validate_tool_availability`` — all
for the host-side legacy dispatcher path. Kept the ``register_tool``
decorator (sandbox side), ``get_tool_by_name``, ``get_tool_names``,
``tools`` list, ``clear_registry``.

The Jinja prompt template's ``{{ get_tools_prompt() }}`` injection
is dropped — the SDK auto-generates tool descriptions from function
signatures, so the legacy XML tool block was redundant and stale.

#4 Delete every ``*_actions_schema.xml`` (12 files). They were read
by the now-removed ``_load_xml_schema`` to build the legacy prompt's
tool descriptions. No consumer remains.

Side fixes:
- ``reporting_renderer.py`` updated to import ``_parse_*_xml`` from
  the new location with leading underscore.
- ``test_local_tools.py``, ``test_notes_jsonl_concurrency.py``,
  ``test_notes_wiki.py`` updated to point at the new module paths
  and call the ``_*_impl`` sync helpers.

Tests: 279/279 passing. ~1500 LOC of action files moved into the
tool wrappers; ~140 LOC of registry boilerplate removed; ~400 lines
of dead XML deleted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
0xallam
2026-04-25 11:26:02 -07:00
parent 572ef2a2af
commit dc9b9f5f9c
40 changed files with 1459 additions and 4372 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
from .thinking_actions import think
from .tool import think
__all__ = ["think"]
-18
View File
@@ -1,18 +0,0 @@
from typing import Any
from strix.tools.registry import register_tool
@register_tool(sandbox_execution=False)
def think(thought: str) -> dict[str, Any]:
try:
if not thought or not thought.strip():
return {"success": False, "message": "Thought cannot be empty"}
return {
"success": True,
"message": f"Thought recorded successfully with {len(thought.strip())} characters",
}
except (ValueError, TypeError) as e:
return {"success": False, "message": f"Failed to record thought: {e!s}"}
@@ -1,54 +0,0 @@
<tools>
<tool name="think">
<description>Use the tool to think about something. It will not obtain new information or change the
database. Use it when complex reasoning or some cache memory is needed.</description>
<details>This tool creates dedicated space for structured thinking during complex tasks,
particularly useful for:
- Tool output analysis: When you need to carefully process the output of previous tool calls
- Policy-heavy environments: When you need to follow detailed guidelines and verify compliance
- Sequential decision making: When each action builds on previous ones and mistakes are costly
- Multi-step problem solving: When you need to break down complex problems into manageable steps</details>
<parameters>
<parameter name="thought" type="string" required="true">
<description>The thought or reasoning to record</description>
</parameter>
</parameters>
<returns type="Dict[str, Any]">
<description>Response containing: - success: Whether the thought was recorded successfully - message: Confirmation message with character count or error details</description>
</returns>
<examples>
# Planning and strategy
<function=think>
<parameter=thought>Analysis of the login endpoint SQL injection:
Current State:
- Confirmed SQL injection in POST /api/v1/auth/login
- Backend database is PostgreSQL 14.2
- Application user has full CRUD privileges
Exploitation Strategy:
1. First, enumerate database structure using UNION-based injection
2. Extract user table schema and credentials
3. Check for password hashing (MD5? bcrypt?)
4. Look for admin accounts and API keys
Risk Assessment:
- CVSS Base Score: 9.8 (Critical)
- Attack Vector: Network (remotely exploitable)
- Privileges Required: None
- Impact: Full database compromise
Evidence Collected:
- Error-based injection confirms PostgreSQL
- Time-based payload: admin' AND pg_sleep(5)-- caused 5s delay
- UNION injection reveals 8 columns in users table
Next Actions:
1. Write PoC exploit script in Python
2. Extract password hashes for analysis
3. Create vulnerability report with full details
4. Test if same vulnerability exists in other endpoints</parameter>
</function>
</examples>
</tool>
</tools>
+10 -12
View File
@@ -1,19 +1,10 @@
"""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.
"""
"""``think`` — record a private chain-of-thought note with no side effects."""
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)
@@ -28,5 +19,12 @@ async def think(thought: str) -> str:
Args:
thought: The agent's reasoning to record. Must be non-empty.
"""
result = _legacy_think(thought)
return json.dumps(result, ensure_ascii=False)
if not thought or not thought.strip():
return json.dumps({"success": False, "message": "Thought cannot be empty"})
return json.dumps(
{
"success": True,
"message": (f"Thought recorded successfully with {len(thought.strip())} characters"),
},
ensure_ascii=False,
)