Execution Engine
Every entry point (TUI, /v1/send, /v1/chat/completions, Telegram, Discord, scheduler) goes through the same startup: exec.Prepare matches a skill from the input, then exec.Start resolves a skill named by the caller, emits the skill and model selection events, picks the model and its fallbacks through ResolveAgent, and hands off to exec.Execute(). exec.Run was removed; use exec.Prepare + exec.Start.
exec.Execute() runs the main loop for up to 128 iterations (limits.max_tool_iterations). Each iteration:
- Assemble messages:
SystemPrompts+OldHistories+UserInput+ToolHistories - Call
Agent.Send()on the chosen provider - Parse
tool_callsfrom the response - Dispatch the tool calls through
toolCall.go(three-pass concurrency, see below) - Append results to
ToolHistories - Stop when no
tool_callsremain or the iteration limit is hit
There is no inter-round delay — rate-limit protection comes from provider round-trip latency, per-model cooldown, and per-tool timeouts.
Three-pass tool concurrency
toolCall.go splits each round's tool calls into three serial passes; only Pass 2 fans out:
| Pass | Mode | Work |
|---|---|---|
| 1 — pre-flight | Serial | Duplicate-call dedupe (tool|args hash), stub-tool short-circuit, confirm gate, JSON-schema validation |
| 2 — execute | Concurrent for IsConcurrent-tagged tools; serial otherwise |
tools.Execute |
| 3 — commit | Serial | Land sessionData.Tools and ToolHistories, update the dedupe map, write the result cache, emit EventToolResult |
Concurrent-tagged built-in tools (17): read_files, find_files, find_tools, find_note, file_history, chat_history, error_history, fetch_page, search_web, http_request, download_file, test_tool, calculate, subagents, list_chatbot, send_to_chatbot, reasoning_guide. generate_audio and generate_image do not carry the flag. api_* / script_* / ext_* tools run concurrently only when their own definition declares it. edit_file, run_command, interactive tools, and MCP tools always run serially.
Dedupe is per run, not per session: a repeated tool|args pair returns the earlier result instead of executing again, and edit_file invalidates the read_files entries covering the paths it touched. A separate 30-minute result cache in ToriiDB (db_0) covers fetch_page, search_web, and http_request GET calls only.
Pending registry
internal/runtime/pending.go is the prefix-routed confirm/ask registry shared by the main agent and any in-process subagents. Producers (toolCall confirm, ask_user handler, store_secret handler) call Ask(ctx, req) and block on a per-entry buffered=1 reply channel. Each front-end registers a listener via RegisterListener(prefix) — the TUI uses "cli-", the web dashboard "chat-", and the Telegram / Discord listeners "tg-" / "dc-" (an empty prefix matches everything) — and claims only matching entries through PickNext(prefix) or PickNextMatch(prefix, accept). PickNextFor was removed; use PickNext / PickNextMatch. ctx cancellation removes the entry so a stale producer never wastes a human interaction.
The gate HasListener(origin) checks whether a listener whose prefix matches the session's origin (OriginOf recognizes cli-, chat-, tg-, dc-) is registered. This replaces the old global pending.Active atomic.Bool, so TUI, web, Telegram, and Discord confirm flows run side by side without blocking each other.
Send failure handling
Agent.Send() failures escalate instead of retrying blindly:
| Failure | Behavior |
|---|---|
| Timeout | Up to MaxSendTimeoutRetries (3) attempts on the same model, spaced by SendTimeoutRetryInterval (15 s) |
| Rate limit (HTTP 429 or a rate-limit message) | Register a 30-minute cooldown, retry the same model after 5 s, 10 s, then 15 s; then switch to the next fallback |
| Quota exhausted (HTTP 402 / 403 or a quota / billing message) | Register a 30-minute cooldown and switch to the next fallback immediately |
| Context-length exceeded | Trim the oldest exchange (compact.TrimFallback) and resend; abort only when nothing is left to trim |
| No response while streaming | Health-probe every 30 s (UnresponsiveProbeInterval), retry a failed probe every 10 s, switch model after 3 failures |
| Any other error | Switch to the next fallback model; abort when no healthy fallback remains |
Fallback candidates come from ResolveAgent's ordered list. nextAgent skips models from the failed model's provider and models whose context window is smaller than the current input, health-checks each candidate (HealthCheckTimeout, 10 s), and rebuilds the list for up to 3 rounds (maxFallbackRounds). A session bound to a specific model (anything other than auto) never falls back.
Switching models clears ToolHistories (or applies compact.RawToolFallback), resets the dedupe map, and restarts the counters — a fresh model never inherits the failed model's partial state.
Cross-turn workdir reset
The workdir is supplied per request, not carried over from history: the TUI passes its own working directory, /v1/send resolves the work_dir field, and subagents inherit or resolve theirs. GetSession rebuilds the system prompt on every run with that directory, so a cd in an earlier turn does not persist.
- System prompt —
Work directory: {{.WorkPath}}line plus an explicit rule that{{.WorkPath}}is the authoritative base for this turn, that stale history mentions are ignored, and that everyrun_commandalready starts there
The per-message metadata header (timestamp + working directory wrapped around each user message) and its TUI stripper stripUserMetaHeader were removed; the system-prompt {{.WorkPath}} line is the only workdir anchor.
[!NOTE] This document was auto-generated by Claude after reading the full source code.