Execution Flow
This page traces a task from the moment a user submits a message through DAG resolution, tool dispatch, sub-agent calls, and final result delivery.
High-level flow
Section titled “High-level flow”User message ↓Chat API endpoint (/api/chat) ↓Session Manager ← Loads agent config, memory context ↓ExecutionEngine ↓ ├─ Task planner ← Decomposes goal into tasks if project context │ ↓ │ DAG Resolver ← Resolves task order from dependencies │ ↓ │ Task Executor ← Runs tasks sequentially or in parallel ↓Agent Loop ← Multi-turn conversation with LLM │ ├─ LLM API call ← Anthropic/OpenAI/etc. │ ├─ Tool use response │ ↓ │ ToolDispatcher │ ├─ Built-in tools (bash, file, web_search, browser, ...) │ ├─ MCP tools (mcp__server__tool_name) │ └─ Sub-agent (spawn_sub_agent) │ ↓ │ SubAgentLifecycle │ ↓ │ [Nested agent loop] │ ↓ │ ResultAggregator ↓Result delivery ← SSE stream to UI / channel message ↓Memory update ← Episode storedStep 1: Request ingestion
Section titled “Step 1: Request ingestion”The chat API endpoint receives a POST:
POST /api/chat/{agent_id}{"message": "Analyze the test failures in the CI pipeline"}It:
- Validates auth (API key check)
- Loads the agent config from
agentstable - Creates or resumes a session (
session_id) - Starts an SSE response stream
Step 2: Session setup
Section titled “Step 2: Session setup”The SessionManager:
- Loads recent episodic memory (configurable window, default last 20 episodes)
- Runs semantic retrieval to find relevant past context
- Constructs the system prompt with agent personality + memory context
- Injects any project/task context if the message is part of a project
Step 3: Agent loop (multi-turn)
Section titled “Step 3: Agent loop (multi-turn)”The core execution is an agentic loop:
while True: response = await llm.create( model=agent.model, system=system_prompt, messages=conversation_history, tools=available_tools, )
if response.stop_reason == "end_turn": break # LLM is done
if response.stop_reason == "tool_use": for tool_call in response.tool_uses: result = await tool_dispatcher.dispatch(tool_call) conversation_history.append(tool_result(tool_call.id, result))
# Continue loop with updated historyMaximum turns: configurable per agent (default: 50).
Step 4: Tool dispatch
Section titled “Step 4: Tool dispatch”ToolDispatcher routes tool calls to their handler:
tool_name ├─ "bash" → BashTool.execute() ├─ "read_file" → FileTool.read() ├─ "write_file" → FileTool.write() ├─ "web_search" → BraveSearchTool.search() ├─ "browser_navigate" → BrowserManager.navigate() ├─ "spawn_sub_agent" → SubAgentSpawner.spawn() └─ "mcp__*__*" → MCPConnectionManager.call_tool()Tool results flow back into the conversation as tool_result messages.
Step 5: Sub-agent execution
Section titled “Step 5: Sub-agent execution”When the agent calls spawn_sub_agent:
SubAgentSpawner.spawn()validates permissions- Creates a
SubAgentrecord in the database - Starts an isolated session (with filtered tool set)
- Runs the sub-agent’s own agent loop concurrently
- Returns partial or final result to the parent
ResultAggregatormerges results using the configured strategy
Sub-agents can spawn their own sub-agents (up to depth 3).
Step 6: Approval gates
Section titled “Step 6: Approval gates”Certain actions require human approval before proceeding:
- Execution pauses
ApprovalRequestis created and streamed to the UI- Agent waits (up to configurable timeout)
- On approval: continues; on rejection: tool result is “action rejected by user”
Step 7: Memory update
Section titled “Step 7: Memory update”After the session ends (user stops interacting or max turns reached):
- The entire conversation is serialized as episodic memory entries
- If a vector store is configured, embeddings are computed and stored
- The knowledge graph is updated with any new entities discovered
Step 8: Result streaming
Section titled “Step 8: Result streaming”Throughout execution, the SSE stream delivers:
event: deltadata: {"text": "I'll analyze the CI failures...", "session_id": "sess_abc"}
event: tool_usedata: {"tool": "bash", "input": {"command": "cat ci.log | grep FAILED"}}
event: tool_resultdata: {"tool": "bash", "output": "3 tests failed: test_auth, test_payment..."}
event: deltadata: {"text": "Found 3 test failures. The root cause is..."}
event: donedata: {"session_id": "sess_abc", "token_usage": {"input": 1842, "output": 547}}The UI renders these events in real time.
Project task execution
Section titled “Project task execution”When an agent is working on a project (not a free-form chat), the flow changes:
- Project goal is decomposed into tasks by the task planner
- Tasks are arranged in a DAG based on
depends_onrelationships - The
DAGResolvercomputes the execution order - Tasks execute per the project’s
execution_strategy:- sequential: one at a time
- parallel: all independent tasks simultaneously
- dag: topological order with concurrency where possible
- Each task runs its own agent loop
- Project status updates as tasks complete (
in_progress→completed)
Retry and failure handling
Section titled “Retry and failure handling”Tasks that fail are classified by failure type:
| Class | Examples | Default retry |
|---|---|---|
transient | Network timeout, rate limit | Yes — up to 3x with backoff |
tool_error | Bash command failed | Yes — up to 2x |
llm_error | Model API error | Yes — 1x after 10s |
context_limit | Token limit exceeded | Compress context and retry |
permanent | Invalid tool args | No |
human_rejected | User rejected action | No |
Retry delays use exponential backoff: 10s, 30s, 90s.
Concurrency
Section titled “Concurrency”Multiple agent sessions can run concurrently. Each session has its own:
- Conversation history
- Active tool calls
- Sub-agent tree
Limits:
- Max concurrent sessions per agent: 5 (configurable)
- Max concurrent sub-agents globally: 8
- Max concurrent workflow runs: 10 (configurable via
WORKFLOW_MAX_CONCURRENT_RUNS)