| 86 | |
| 87 | |
| 88 | class LiveAgentRunner: |
| 89 | def __init__( |
| 90 | self, |
| 91 | *, |
| 92 | provider: Any, |
| 93 | tool_registry: Any, |
| 94 | parent_context: Any, |
| 95 | base_tools: list, |
| 96 | resolve_agent: Callable[[str], Any], |
| 97 | default_agent_type: str = "general-purpose", |
| 98 | run_id: str = "wf", |
| 99 | max_turns: Optional[int] = None, |
| 100 | schema_max_attempts: int = 3, |
| 101 | ) -> None: |
| 102 | self._provider = provider |
| 103 | self._tool_registry = tool_registry |
| 104 | self._parent_context = parent_context |
| 105 | self._base_tools = list(base_tools) |
| 106 | self._resolve_agent = resolve_agent |
| 107 | self._default_agent_type = default_agent_type |
| 108 | self._run_id = run_id |
| 109 | self._max_turns = max_turns |
| 110 | # A schema agent that fails validation (or skips the tool) is re-run with |
| 111 | # a corrective prompt, up to this many TOTAL attempts. Retries cost extra |
| 112 | # only on failure — a model that gets it right first time pays nothing. |
| 113 | self._schema_max_attempts = max(1, schema_max_attempts) |
| 114 | |
| 115 | async def run(self, spec: AgentSpec, *, abort: AbortController, index: str) -> AgentOutcome: |
| 116 | # isolation="worktree": run the agent in a throwaway git worktree so |
| 117 | # parallel file-mutating agents don't collide. Best-effort — if the |
| 118 | # worktree can't be created the agent runs in place. |
| 119 | if spec.isolation == "worktree": |
| 120 | import dataclasses |
| 121 | from pathlib import Path as _Path |
| 122 | |
| 123 | from src.workflow.worktree import agent_worktree |
| 124 | |
| 125 | base_cwd = str(self._parent_context.cwd) if getattr(self._parent_context, "cwd", None) else "." |
| 126 | async with agent_worktree(self._run_id, index, base_cwd) as wt: |
| 127 | context = ( |
| 128 | dataclasses.replace(self._parent_context, cwd=_Path(wt)) |
| 129 | if wt |
| 130 | else self._parent_context |
| 131 | ) |
| 132 | return await self._run_in_context(spec, context, abort=abort, index=index) |
| 133 | return await self._run_in_context(spec, self._parent_context, abort=abort, index=index) |
| 134 | |
| 135 | async def _run_in_context( |
| 136 | self, spec: AgentSpec, parent_context: Any, *, abort: AbortController, index: str |
| 137 | ) -> AgentOutcome: |
| 138 | # Imported lazily: ``src.agent`` pulls in the whole agent stack, which |
| 139 | # the engine core deliberately never imports. |
| 140 | from src.agent.agent_tool_utils import finalize_agent_tool, resolve_agent_tools |
| 141 | from src.agent.constants import ALL_AGENT_DISALLOWED_TOOLS, WORKFLOW_TOOL_NAME |
| 142 | from src.agent.run_agent import RunAgentParams, run_agent |
| 143 | from src.tasks.progress import ProgressTracker, update_progress_from_message |
| 144 | from src.tool_system.registry import ToolRegistry |
| 145 | from src.types.messages import AssistantMessage, UserMessage |
no outgoing calls