(registry: Any, provider: Any)
| 88 | |
| 89 | |
| 90 | def _default_runner_factory(registry: Any, provider: Any) -> Callable[[ToolContext, str], Any]: |
| 91 | def factory(context: ToolContext, run_id: str) -> Any: |
| 92 | from src.workflow.runner import LiveAgentRunner |
| 93 | |
| 94 | def resolve(agent_type: str) -> Any: |
| 95 | from src.agent.agent_definitions import GENERAL_PURPOSE_AGENT |
| 96 | try: |
| 97 | from src.agent.agent_definitions import find_agent_by_type |
| 98 | from src.agent.load_agents_dir import get_agent_definitions_with_overrides |
| 99 | |
| 100 | agents = get_agent_definitions_with_overrides(str(context.cwd or ".")) |
| 101 | found = find_agent_by_type(agents, agent_type) |
| 102 | return found or GENERAL_PURPOSE_AGENT |
| 103 | except Exception: |
| 104 | logger.exception("agent resolution failed; falling back to general-purpose") |
| 105 | return GENERAL_PURPOSE_AGENT |
| 106 | |
| 107 | # Cap per-agent turns. The subagent default (30) lets an aimless model |
| 108 | # (e.g. deepseek-flash) loop WebSearch/WebFetch ~30 times without ever |
| 109 | # finalizing via StructuredOutput — observed as agents "stuck" for |
| 110 | # minutes and burning ~30x the tokens. Workflow agents do focused tasks |
| 111 | # (search a few sources → emit; verify → emit; synthesize → write), so a |
| 112 | # tighter bound forces progress. Disciplined models finish well under it. |
| 113 | # Env-tunable for workflows that genuinely need deeper agents. |
| 114 | import os |
| 115 | |
| 116 | max_turns = int(os.environ.get("CLAWCODEX_WORKFLOW_MAX_TURNS", "18")) |
| 117 | |
| 118 | return LiveAgentRunner( |
| 119 | provider=provider, |
| 120 | tool_registry=registry, |
| 121 | parent_context=context, |
| 122 | base_tools=list(registry.list_tools()), |
| 123 | resolve_agent=resolve, |
| 124 | run_id=run_id, |
| 125 | max_turns=max_turns, |
| 126 | ) |
| 127 | |
| 128 | return factory |
| 129 | |
| 130 | |
| 131 | def make_workflow_tool( |
no outgoing calls
no test coverage detected