mirror of
https://github.com/usestrix/strix.git
synced 2026-10-01 02:03:55 +00:00
Support native fix-agent assignments and final reviewed patches
This commit is contained in:
parent
d184142377
commit
348fbf2608
6 changed files with 49 additions and 16 deletions
|
|
@ -668,6 +668,7 @@ def build_strix_agent(
|
|||
system_prompt_context: dict[str, Any] | None = None,
|
||||
extra_tools: Sequence[Tool] | None = None,
|
||||
instructions_override: str | None = None,
|
||||
base_tools: Sequence[Tool] | None = None,
|
||||
) -> SandboxAgent[Any]:
|
||||
"""Build a SandboxAgent for either root or child use.
|
||||
|
||||
|
|
@ -680,6 +681,8 @@ def build_strix_agent(
|
|||
registered via ``register_agent_tools``.
|
||||
instructions_override: Use this verbatim as the system prompt instead
|
||||
of rendering the built-in scan prompt.
|
||||
base_tools: Replace the scan toolset (including registered scan extras)
|
||||
for specialized assignments. Filesystem, shell and completion remain available.
|
||||
"""
|
||||
if instructions_override is not None:
|
||||
instructions = instructions_override
|
||||
|
|
@ -694,14 +697,15 @@ def build_strix_agent(
|
|||
system_prompt_context=system_prompt_context,
|
||||
)
|
||||
|
||||
agent_tools = [*_EXTRA_TOOLS, *(extra_tools or [])]
|
||||
selected_tools = list(_BASE_TOOLS if base_tools is None else base_tools)
|
||||
agent_tools = [*(_EXTRA_TOOLS if base_tools is None else []), *(extra_tools or [])]
|
||||
if interactive:
|
||||
# Yielding to the user is only meaningful when one is attached.
|
||||
agent_tools.append(respond_to_user)
|
||||
if is_root:
|
||||
tools: list[Tool] = [*_BASE_TOOLS, *agent_tools, finish_scan]
|
||||
tools: list[Tool] = [*selected_tools, *agent_tools, finish_scan]
|
||||
else:
|
||||
tools = [*_BASE_TOOLS, *agent_tools, agent_finish]
|
||||
tools = [*selected_tools, *agent_tools, agent_finish]
|
||||
_ensure_unique_tool_names(tools)
|
||||
tools = [
|
||||
_with_bounded_result(_with_strictness(_with_coerced_arguments(tool), strict_tool_schemas))
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ from strix.fix.prepare import (
|
|||
build_git_manifest,
|
||||
build_git_patch,
|
||||
prepare_fix,
|
||||
workspace_digest,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -73,4 +74,5 @@ __all__ = [
|
|||
"build_git_patch",
|
||||
"candidate_from_legacy_report",
|
||||
"prepare_fix",
|
||||
"workspace_digest",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ async def _tracked_in_index(workspace: Path, path: str) -> bool:
|
|||
return await process.wait() == 0
|
||||
|
||||
|
||||
async def _workspace_digest(workspace: Path) -> str:
|
||||
async def workspace_digest(workspace: Path) -> str:
|
||||
manifest, _, _ = await build_git_manifest(workspace)
|
||||
payload = json.dumps(
|
||||
[entry.model_dump(mode="json") for entry in manifest],
|
||||
|
|
@ -374,7 +374,11 @@ def _result(
|
|||
state=state,
|
||||
validation_mode="agent_review",
|
||||
prepared_source_digest=(
|
||||
context.feedback[-1].repair.source_digest if context.feedback else None
|
||||
verifier.source_digest
|
||||
if verifier is not None
|
||||
else context.feedback[-1].repair.source_digest
|
||||
if context.feedback
|
||||
else None
|
||||
),
|
||||
test_plan=context.feedback[-1].repair.test_plan if context.feedback else None,
|
||||
stop_reason=reason,
|
||||
|
|
@ -475,14 +479,14 @@ async def prepare_fix( # noqa: PLR0915 - thin orchestration and cleanup
|
|||
record = FixPreparationAttempt(
|
||||
attempt=context.attempt,
|
||||
repair=RepairOutcome(status=RepairStatus.INCOMPLETE, summary="Repair started."),
|
||||
workspace_digest=await _workspace_digest(workspace),
|
||||
workspace_digest=await workspace_digest(workspace),
|
||||
)
|
||||
context.feedback.append(record)
|
||||
record.repair = _repair_outcome(await repair(context, checks))
|
||||
repair_turns += max(1, record.repair.turns_used)
|
||||
checks = await evidence_reader() if evidence_reader else record.repair.command_results
|
||||
record.checks = list(checks)
|
||||
record.workspace_digest = await _workspace_digest(workspace)
|
||||
record.workspace_digest = await workspace_digest(workspace)
|
||||
manifest, _, _ = await manifest_builder(workspace)
|
||||
if record.repair.status is not RepairStatus.COMPLETE:
|
||||
return await finish(
|
||||
|
|
@ -518,15 +522,12 @@ async def prepare_fix( # noqa: PLR0915 - thin orchestration and cleanup
|
|||
if verifier.decision is not VerificationDecision.VERIFIED:
|
||||
return await finish(PreparationState.BLOCKED, verifier.summary, gaps=verifier.gaps)
|
||||
# Test selection, failures, reruns and coverage belong to the reviewer.
|
||||
# Only the artifact identity is checked here; it never starts another repair.
|
||||
if (
|
||||
record.workspace_digest != await _workspace_digest(workspace)
|
||||
or verifier.source_digest != record.repair.source_digest
|
||||
):
|
||||
# Review may correct the patch. Approval binds to its final snapshot,
|
||||
# not the earlier repair checkpoint.
|
||||
if verifier.source_digest != await workspace_digest(workspace):
|
||||
return await finish(
|
||||
PreparationState.BLOCKED,
|
||||
"The deliverable changed during review; "
|
||||
"the approved patch cannot be delivered.",
|
||||
"The deliverable changed after review; the approved patch cannot be delivered.",
|
||||
)
|
||||
return await finish(
|
||||
PreparationState.READY,
|
||||
|
|
|
|||
|
|
@ -619,6 +619,7 @@ async def agent_finish(
|
|||
success: bool = True,
|
||||
report_to_parent: bool = True,
|
||||
final_recommendations: list[str] | None = None,
|
||||
outcome: str | None = None,
|
||||
) -> str:
|
||||
"""Subagent termination — post a completion report to the parent.
|
||||
|
||||
|
|
@ -669,8 +670,12 @@ async def agent_finish(
|
|||
final_recommendations: Optional next-step suggestions for the
|
||||
parent (e.g., "prioritize testing X", "spawn an agent to
|
||||
cover Y").
|
||||
outcome: Optional assignment-specific outcome, when requested by the caller.
|
||||
"""
|
||||
inner = _ctx(ctx)
|
||||
allowed_outcomes = inner.get("completion_outcomes")
|
||||
if allowed_outcomes is not None and outcome not in allowed_outcomes:
|
||||
return json.dumps({"success": False, "error": f"Choose an outcome: {allowed_outcomes}"})
|
||||
coordinator = coordinator_from_context(inner)
|
||||
me = inner.get("agent_id")
|
||||
if coordinator is None or me is None:
|
||||
|
|
@ -745,6 +750,10 @@ async def agent_finish(
|
|||
"parent_notified": parent_notified,
|
||||
"agent_id": me,
|
||||
"summary": result_summary,
|
||||
"outcome": outcome,
|
||||
"task_success": success,
|
||||
"open_items": list(open_items or []),
|
||||
"recommendations": list(final_recommendations or []),
|
||||
"filed_report_ids": filed_report_ids,
|
||||
"findings_count": len(findings or []),
|
||||
"open_items_count": len(open_items or []),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from agents.tool import CustomTool, FunctionTool
|
|||
|
||||
from strix.agents import factory
|
||||
from strix.config import load_settings
|
||||
from strix.tools.thinking.tool import think
|
||||
|
||||
|
||||
def _capturing_exec_tool(captured: dict[str, str]) -> FunctionTool:
|
||||
|
|
@ -115,3 +116,19 @@ def test_function_tools_are_result_bounded() -> None:
|
|||
by_name = {t.name: t for t in agent.tools}
|
||||
|
||||
assert getattr(by_name["think"], "_strix_bounded", False) is True
|
||||
|
||||
|
||||
def test_specialized_tools_do_not_inherit_scan_or_registered_tools(monkeypatch) -> None:
|
||||
extra = _capturing_exec_tool({})
|
||||
extra.name = "scan_extension"
|
||||
monkeypatch.setattr(factory, "_EXTRA_TOOLS", [extra])
|
||||
default = factory.build_strix_agent(is_root=False)
|
||||
specialized = factory.build_strix_agent(is_root=False, base_tools=[think])
|
||||
default_names = {tool.name for tool in default.tools}
|
||||
specialized_names = {tool.name for tool in specialized.tools}
|
||||
|
||||
assert {"scan_extension", "create_agent", "record_coverage"} <= default_names
|
||||
assert {"think", "agent_finish"} <= specialized_names
|
||||
assert (
|
||||
not {"scan_extension", "create_agent", "record_coverage", "finish_scan"} & specialized_names
|
||||
)
|
||||
|
|
|
|||
|
|
@ -38,11 +38,11 @@ from strix.fix.prepare import (
|
|||
PreparationContext,
|
||||
PreparationPolicy,
|
||||
_network_isolation_prefix,
|
||||
_workspace_digest,
|
||||
build_git_manifest,
|
||||
build_git_patch,
|
||||
prepare_fix,
|
||||
run_command,
|
||||
workspace_digest,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ async def _noop_repair(
|
|||
),
|
||||
*context.request.checks,
|
||||
]
|
||||
digest = await _workspace_digest(context.workspace)
|
||||
digest = await workspace_digest(context.workspace)
|
||||
results = []
|
||||
for command in commands:
|
||||
result = record_test_execution(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue