Documentation v1.0.9

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:

  1. Assemble messages: SystemPrompts + OldHistories + UserInput + ToolHistories
  2. Call Agent.Send() on the chosen provider
  3. Parse tool_calls from the response
  4. Dispatch the tool calls through toolCall.go (three-pass concurrency, see below)
  5. Append results to ToolHistories
  6. Stop when no tool_calls remain 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.

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.

中文