15. Sub-tasks and background execution

Complex tasks often need to be parcelled out to "sub-assistants". AVL Code has built-in sub-task scheduling, with both synchronous and asynchronous modes.

15.1 Dispatching sub-tasks

The assistant can call a "sub-assistant" tool that runs a given prompt in an isolated sub-task. The sub-task completes and the result merges back into the main conversation. This is transparent to you; the tool-call card shows the sub-task details.

15.1.1 Sub-agents inherit external MCP tools

Dispatched sub-agents automatically inherit the external MCP tools you configured for the workspace (previously sub-tasks couldn't use these external tools and you had to come back to the main conversation):

  • Mechanism: at dispatch time, AVL Code calls /admin/routes + /admin/backends to enumerate every external backend's tool namespace in this workspace (e.g. notion / memory / channels) and injects them via the ZAGENT_MCP_INHERIT_PREFIXES env var; the sub-agent's ZAGENT_ALLOWED_TOOLS whitelist passes those prefixes through unconditionally.
  • Coverage: all sub-agents that declare tools: (plan / assess / prepare / mcp-admin and any user-defined ones); uniform across all work modes (plan included).
  • Fail-safe: any failure to resolve returns empty → no inheritance, sub-task dispatch is not affected.

15.1.2 Sub-task model / provider matching fixed

Previously, under multi-provider configurations, sub-tasks inherited the parent session's model but the provider was always taken as the first in the list — a mismatch would surface as "model not found" and the sub-task wouldn't start. This release picks the provider that follows the inherited model — preferring the one that actually contains that model — so sub-tasks start reliably.

15.1.3 Parallel fan-out: split into many legs at once

Beyond dispatching one sub-task at a time, the AI can use the AgentParallel tool to split a task into 2–16 "legs" running simultaneously — each an independent fresh-context sub-agent, mutually invisible — and aggregate the results automatically when all finish. Ideal for genuinely concurrent work (e.g. reviewing several modules or searching multiple sources at once); much faster than queueing them serially.

  • Each leg carries its own prompt and can set its own subagent type and model; the prompt must be self-contained (a leg can't see the main chat history).
  • Concurrency caps at 16 legs (well below the background-task cap of 64, so one fan-out can't monopolize all slots); fewer than 2 should use a single Agent.
  • The overall wait times out at 600 s by default, 1800 s max; legs not done by then return pending and keep running in the background — follow up with TaskWait / TaskOutput.
  • A failed / blocked leg doesn't drag the batch down; its reason comes back with the results for the AI to handle. Over-long leg output is truncated (with a note to fetch the full text via TaskOutput).
  • Live status while running: the parallel call card appears during execution (no waiting for the terminal state) and reflects each sub-task's running status in real time; it stops spinning once stopped, and the result block isn't wrongly collapsed in compact density.

15.2 Foreground vs background

  • Foreground: the main assistant waits for the sub-task (default 10-min timeout); good for must-wait tasks.
  • Background: the sub-task runs asynchronously and the main assistant gets a task id immediately; later it can pull status or stop the task at will. Background tasks have a cap (preventing fan-out) and auto-cleanup (terminal states age out).

15.2.1 Idle-based watchdog + absolute ceiling

Sub-task timeout is now idle-based: as long as it keeps producing output, it isn't considered timed out — the watchdog only catches the truly stuck, no-output-for-a-while case. On top of that there's an absolute ceiling (default 60 min) so an extreme infinite loop / hang can't keep the task running forever.

  • Idle window: 15 minutes with no output = timeout (error reads "subagent idle timeout").
  • Absolute ceiling: 60 minutes hard stop (error reads "absolute ceiling").
  • Long-running healthy tasks are unaffected: any stream output (including thinking blocks, incremental characters) resets the idle counter.
  • RunAgentToolBackground background path doesn't enforce an execution timeout (if you explicitly background it, you accept it may run a while).

15.3 Concurrency limit

To prevent runaway fan-out, AVL Code caps simultaneously-running sub-tasks. Above the cap the assistant is told "wait a bit before launching" — no avalanches.

15.3.1 Block-and-wait — the TaskWait tool

The main assistant can call once and block until background tasks / sub-agents complete, instead of polling round after round.

  • Wait for all or any to finish.
  • Configurable timeout (default 5 min, max 30 min); on timeout it returns "still running" so the model can decide whether to keep waiting.
  • During the wait only status and the final result are returned — intermediate progress doesn't get fed back into the main conversation, saving context and tokens.

15.4 Termination

  • On desktop, the Stop button to the right of the input box cancels the main assistant and all sub-tasks together.
  • From WeChat, /stop does the same cascade.
  • Sub-tasks can also be stopped individually: each running sub-task in the sub-agent drawer has its own stop button, so you don't have to stop the whole turn (handy for background / parallel fan-out legs; synchronous inline sub-agents still require stopping the whole turn).

15.4.1 Real total consumption is visible (incl. sub-agents)

Usage stats now include the real consumption of sub-agents (parallel / sub-tasks): the GoalStrip and the end-of-run marker both show the real total token consumption + tool-call count, and break it down into "self + subagents" when sub-agents are present. This is persisted per session, so a long session shows the true running total throughout (it accumulates even without a goal set).

15.5 Output aggregation

Background tasks' intermediate logs appear as their own cards in the main stream you can expand; the final result comes back as a tight summary, not flooding the main thread.

15.5.1 AI Suggested Tasks: one-click to split off

While the AI is working, if it notices out-of-scope but worth doing ideas (dead code, stale docs, confirmed TODOs, security concerns, etc.), it surfaces a "Suggested Task" card in the message stream rather than derailing the current task.

The execute button on the card is a split button:

  • Primary: default "Run in a new session".
  • Dropdown ▾: four more options:
    • Run in a new session (primary default)
    • Run in the current session
    • Copy to input box
    • Copy to clipboard

You decide how to handle it. Executed suggestions are tagged; the AI can also withdraw stale ones. Multiple suggestions aggregate into a strip above the input box for unified management.

15.6 Routines: let the AI run on a schedule

A "Routine" lets you give a workspace a fixed instruction the AI runs on schedule or on demand — e.g. "every morning, pull and summarise yesterday's issues."

Entry:

  • The workspace sidebar has "New Routine" (also reachable from the current-workspace header dropdown).
  • Form fields: name, instruction, execution mode, model (same picker as a normal conversation: mode color cards and model search-by-group), schedule.
  • Auto-saved draft as you write: jumping to Settings or the agent manager mid-form and coming back automatically reopens and restores your in-progress entries.

Schedule types:

Type Behaviour
Hourly + minute Triggers once every hour at the chosen minute; reuses the HH:MM field but only the minute matters — hour is locked to 00, the trigger label shows :MM
Daily / Weekdays / Weekly + time of day Auto-triggered at the time
No schedule (manual) Doesn't auto-run; on save it runs once and the button reads "Run once"; trigger manually thereafter

Time picker is ergonomic:

  • Hour / minute two-column dropdowns (selected option centred; choosing minutes auto-collapses); hourly mode shows only the minute column.
  • Weekday selection uses circular single-character chips, with "Weekdays / Weekend" one-click presets.
  • Schedule precision: 30-second tick; hourly handles day boundaries / DST correctly via time arithmetic.

Unified management:

In Settings → Routines you can see everything by source workspace:

  • Toggle on/off (same Switch component as elsewhere).
  • Run now.
  • See recent run traces.
  • Workspaces not currently open have a badge and a one-click "Open".
  • The sidebar icon jumps straight to this workspace's routines list.
  • Search by name / workspace.

Run behaviour:

  • Each run creates a regular session in the corresponding workspace; you can review the process and the result.
  • A routine that's still running skips the next trigger — no stacking.

15.7 Long-running tasks recover after a crash

A long task you put in the background (a sub-task dispatched with run_in_background) has its progress persisted to disk in real time; if the app exits unexpectedly, is killed, crashes, or is interrupted by an upgrade, reopening it lets you pick the unfinished task back up rather than starting over.

How it works:

  • Each non-terminal background task is written as a snapshot on disk (a global, cross-workspace directory); when a task reaches a terminal state (done / error / killed) the snapshot is deleted. So a snapshot still on disk after a restart is a task that was interrupted and never finished.
  • On startup these leftover tasks are scanned asynchronously: first a "dirty-entry cleanup" removes entries whose parent session no longer exists (which can never be recovered), then the remaining tasks are listed. The scan never blocks startup, and with nothing leftover it's silent.
  • Recovery doesn't directly respawn the sub-process; instead it injects a "continue / re-delegate this unfinished background task" message into the original parent session, letting the main agent finish it agent-first — and this step is visible, so you see "recovering task X".

What you see (the recovery banner):

  • When interrupted tasks exist, a banner appears at the top titled like "N background task(s) were interrupted last run"; otherwise it isn't shown.
  • Each row has two buttons: Resume (— injects the continue message and removes the entry from disk) and Ignore (deletes it from disk so it won't prompt again). A top-right "Ignore all" is also available.
  • On success a toast confirms "Resumed: the agent will continue this task in its session"; on failure it shows the specific reason (e.g. the parent session was deleted, and the dirty entry is cleared).

Auto-resume (optional, off by default):

  • Turn on "Auto-resume interrupted tasks" under Settings → Agent → Reflection and a restart will automatically continue background tasks that were unfinished when the app last crashed or was killed — ideal for unattended / overnight long runs.
  • To prevent a "continue-message storm", startup auto-resume handles at most 5 tasks; the rest stay in the top banner for manual resume.
  • When the toggle is off (default), all interrupted tasks are simply listed at the top for you to resume or ignore.