fix: address audit findings — SDK plumbing, TUI bus, dead code

Critical fixes:

- ``StrixOrchestrationHooks.on_agent_start`` now finds the
  ``CaidoCapability`` via ``ctx.context['caido_capability']`` instead
  of ``agent.capabilities`` (we use plain ``Agent``, not
  ``SandboxAgent``, so the latter never existed). The session
  manager's bundle already exposes the capability; ``run_strix_scan``
  threads it through ``make_agent_context`` and ``create_agent``
  forwards it to children.

- ``run_strix_scan`` registers the ``StrixTracingProcessor`` with the
  SDK's tracing provider via ``add_trace_processor`` so SDK trace
  spans hit ``run_dir/events.jsonl`` (was previously a parallel stream
  the SDK ignored).

- ``on_llm_end`` now writes to ``Tracer.record_llm_usage`` in
  addition to ``bus.record_usage`` so the CLI/TUI stats panel sees
  real numbers instead of zeros.

- ``run_strix_scan`` accepts an externally-built ``AgentMessageBus``
  + an explicit ``model`` arg. The TUI pre-creates the bus so its
  stop and chat-input handlers can submit ``bus.send`` /
  ``bus.cancel_descendants`` coroutines onto the scan thread's loop
  via ``asyncio.run_coroutine_threadsafe`` — replacing the
  TODO-stub no-ops.

- ``model`` config now propagates root → context → child agents in
  ``create_agent`` (was hardcoded fallback).

Dead-code removal:

- Deleted the ``load_skill`` tool entirely (host module, sandbox
  module, TUI renderer, tests). The legacy implementation reached
  into a global ``_agent_instances`` registry that no longer exists;
  the post-migration stub returned ``success=True`` without
  injecting anything — pure theater. Skills are still preloaded via
  the system prompt at scan-bring-up.

- Dropped ``tenacity`` and ``xmltodict`` from
  ``[project.dependencies]`` — neither is imported anywhere
  post-migration.

- Stripped the system prompt's "use the load_skill tool" lines.

Tests: 278/278 passing. Removed two ``load_skill`` test cases and a
``test_tool_registration_modes::test_load_skill_import_...`` assertion
that exercised the deleted module.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
0xallam
2026-04-25 10:08:35 -07:00
parent af42499b95
commit 572ef2a2af
21 changed files with 98 additions and 318 deletions
-1
View File
@@ -15,7 +15,6 @@ from .agents_graph import * # noqa: F403
from .browser import * # noqa: F403
from .file_edit import * # noqa: F403
from .finish import * # noqa: F403
from .load_skill import * # noqa: F403
from .notes import * # noqa: F403
from .proxy import * # noqa: F403
from .python import * # noqa: F403
-1
View File
@@ -8,7 +8,6 @@ adapter object from the run context.
Used by:
- ``tools/todo/tools.py``
- ``tools/load_skill/tool.py``
- ``tools/finish/tool.py``
"""
+1
View File
@@ -357,6 +357,7 @@ async def create_agent(
sandbox_token=inner.get("sandbox_token"),
tool_server_host_port=inner.get("tool_server_host_port"),
caido_host_port=inner.get("caido_host_port"),
caido_capability=inner.get("caido_capability"),
agent_id=child_id,
agent_name=name,
parent_id=parent_id,
-4
View File
@@ -1,4 +0,0 @@
from .load_skill_actions import load_skill
__all__ = ["load_skill"]
@@ -1,58 +0,0 @@
from typing import Any
from strix.tools.registry import register_tool
@register_tool(sandbox_execution=False)
def load_skill(agent_state: Any, skills: str) -> dict[str, Any]:
try:
from strix.skills import parse_skill_list, validate_requested_skills
requested_skills = parse_skill_list(skills)
if not requested_skills:
return {
"success": False,
"error": "No skills provided. Pass one or more comma-separated skill names.",
"requested_skills": [],
}
validation_error = validate_requested_skills(requested_skills)
if validation_error:
return {
"success": False,
"error": validation_error,
"requested_skills": requested_skills,
"loaded_skills": [],
}
# Runtime skill injection used to reach into the legacy
# ``_agent_instances`` registry to mutate the running LLM's
# active-skills list. The SDK harness owns the agent through
# ``Runner.run`` and there's no equivalent reach-in API yet —
# the model still gets a structured success response so it can
# observe which skills it asked for, even if reload-on-the-fly
# is a Phase 6 follow-up.
newly_loaded = list(requested_skills)
already_loaded: list[str] = []
except Exception as e: # noqa: BLE001
fallback_requested_skills = (
requested_skills
if "requested_skills" in locals()
else [s.strip() for s in skills.split(",") if s.strip()]
)
return {
"success": False,
"error": f"Failed to load skill(s): {e!s}",
"requested_skills": fallback_requested_skills,
"loaded_skills": [],
}
else:
return {
"success": True,
"requested_skills": requested_skills,
"loaded_skills": requested_skills,
"newly_loaded_skills": newly_loaded,
"already_loaded_skills": already_loaded,
"message": "Skills loaded into this agent prompt context.",
}
@@ -1,33 +0,0 @@
<tools>
<tool name="load_skill">
<description>Dynamically load one or more skills into the current agent at runtime.
Use this when you need exact guidance right before acting (tool syntax, exploit workflow, or protocol details).
This updates the current agent's prompt context immediately.</description>
<details>Accepts one skill or a comma-separated skill bundle. Works for root agents and subagents.
Examples:
- Single skill: `xss`
- Bundle: `sql_injection,business_logic`</details>
<parameters>
<parameter name="skills" type="string" required="true">
<description>Comma-separated list of skills to use for the agent (MAXIMUM 5 skills allowed). Most agents should have at least one skill in order to be useful. Agents should be highly specialized - use 1-3 related skills; up to 5 for complex contexts. {{DYNAMIC_SKILLS_DESCRIPTION}}</description>
</parameter>
</parameters>
<returns type="Dict[str, Any]">
<description>Response containing: - success: Whether runtime loading succeeded - requested_skills: Skills requested - loaded_skills: Skills validated and applied - newly_loaded_skills: Skills newly injected into prompt - already_loaded_skills: Skills already present in prompt context</description>
</returns>
<examples>
<function=load_skill>
<parameter=skills>xss</parameter>
</function>
<function=load_skill>
<parameter=skills>sql_injection,business_logic</parameter>
</function>
<function=load_skill>
<parameter=skills>nmap,httpx</parameter>
</function>
</examples>
</tool>
</tools>
-46
View File
@@ -1,46 +0,0 @@
"""SDK function-tool wrapper for the legacy ``load_skill`` tool.
The legacy implementation reaches into ``_agent_instances`` (a global
dict the legacy multi-agent orchestrator maintains) to find the running
``Agent`` instance and call ``agent.llm.add_skills(...)``. That global
goes away under the SDK migration — Phase 3 will replace it with a
context-keyed registry, and this wrapper will be updated to read from
that registry.
For Phase 2 we ship the wrapper as-is. The legacy function falls back
to a clean error path when the agent instance lookup fails, so the
tool degrades gracefully ("Could not find running agent instance...")
until Phase 3 lands. That's better than crashing or stubbing out the
tool entirely — the model still gets a structured error it can react to.
"""
from __future__ import annotations
import asyncio
import json
from typing import Any
from agents import RunContextWrapper
from strix.tools._decorator import strix_tool
from strix.tools._state_adapter import adapter_from_ctx
from strix.tools.load_skill import load_skill_actions as _impl
def _dump(result: dict[str, Any]) -> str:
return json.dumps(result, ensure_ascii=False, default=str)
@strix_tool(timeout=60)
async def load_skill(ctx: RunContextWrapper, skills: str) -> str:
"""Load one or more named skills into this agent's prompt context.
Args:
skills: Comma-separated skill names (max 5). E.g.
``"recon,xss,sqli"``. Skill discovery uses
``strix.skills.parse_skill_list``.
"""
state = adapter_from_ctx(ctx)
return _dump(
await asyncio.to_thread(_impl.load_skill, agent_state=state, skills=skills),
)