18. Subtasks and background execution

[!ref] Full details in the User Manual, "Subtasks and background execution".

18.1 Dispatch params

Arg Type Default Description
prompt string — Subtask prompt; required
agent_id string Current persona Sub-persona to use
model string Current model Subtask model
run_in_background bool false true ⇒ background
timeout_ms int 600_000 Foreground timeout; n/a in background

18.2 BackgroundRegistry

Field Default Description
Concurrent in-flight cap 64 Excess immediately rejected with retry hint
Terminal-state retention 30 min Aged-out beyond this
Priority killed > done External stop wins over natural completion

18.3 Subtask tools

Tool Args Description
Agent prompt, agent_id, run_in_background? Dispatch one subtask
AgentParallel legs[] (2–16), timeout_seconds? Parallel fan-out in one call (see §18.3.1)
TaskOutput task_id, limit? Pull state / output
TaskStop task_id Force-stop
StopAgentToolRun child_session_id Stop one running subagent — background, parallel leg or synchronous inline; idempotent. Optionally keeps the subtask records already produced
TaskWait task_ids, mode=all|any, timeout_seconds (default 300, max 1800) Block-and-wait. The result also carries other_tasks (background tasks in the same session that this wait didn't cover); running tasks carry elapsed_ms
EnterPlanMode / ExitPlanMode none Enter / exit plan
TodoWrite todos Write / update todo

18.3.1 AgentParallel parameters

Param Type Default Notes
legs array — required, 2–16; each an independent fresh-context subagent, aggregated when all finish
legs[].subagent_type string — required, target subagent type
legs[].prompt string — required, self-contained subtask prompt (a leg can't see main chat history)
legs[].description string — optional, 3–5 words, for log / UI only
legs[].model string inherit optional, per-leg model override
timeout_seconds int 600 overall wait, max 1800; legs not done by then return pending and keep running in the background

Fewer than 2 errors out (use Agent); more than 16 are silently truncated to the first 16. Each leg is dispatched as a real background task with one internal Wait(all); per-leg output over 8000 runes is truncated (with a note to fetch full text via TaskOutput). Legs are leaf-level and cannot nest Agent / AgentParallel. v1 is all-mode only. Returns a ParallelResult (completed / num_ok / num_failed / num_pending / legs[] …, never throwing).

Rendering: the call card shows during execution and reflects each leg's running status from the live store (stops spinning once stopped); the result block isn't wrongly collapsed by the tool group in compact density.

18.3.2 Subagent terminal states

State Meaning
Running In flight
Paused Paused by the user; resumable
Done Finished normally
Ended No explicit terminal state on disk (imported session / reload re-seed). These used to be misreported as "Done"
Failed / Stopped / Denied Re-seeded from the on-disk sidecar terminal state
  • A task terminated via TaskStop no longer shows a success mark; MarkKilled settles DurationMS on the spot rather than recording zero.
  • Subagents are only terminated when you explicitly press Stop: respawns, new messages and other internal actions no longer cascade a kill.

18.3.3 Subagent drawer actions

Action Notes
Stop / Pause / Continue "Continue" accepts an optional extra instruction and resumes from the saved run history
Retry Truncate to the last user message and re-run
Run again Re-dispatch with the same type + prompt (disabled when the prompt is missing)
Edit message / Delete message Editing is refused while running; editing uses a side-mounted editor with incremental updates
Delete a tool call Also deletes the paired tool result; imported legacy records without an id cannot be deleted individually
Retry from here Regenerate from this message on (a user message keeps the question and is re-answered; assistant / tool messages redo that step)
Continue from here Keep everything up to this message, discard the rest, and resume from that point; the "extra instruction" field is carried in
Copy / Copy selection —
Copy JSONL path / Copy TaskWait args The latter yields JSON with task_ids + timeout; TaskOutput / TaskStop take the same id

The message-handle menu header shows that message's absolute and relative timestamp; delete / retry / continue all carry an inline confirmation.

18.3.4 TaskWait call-row presentation

Item Behaviour
Expected wait The call row shows a budget of at most timeout_seconds
Overall countdown Started only while in flight and background tasks are actually running; on reaching zero it falls back to a grey static budget instead of sitting at 0ms
Completed state The row's leading icon becomes a clock with a check mark
Compact mode Paired with the subtask status mosaic (one tile per subtask, coloured by state)
Final reply The terminal state shows "Final reply to the main agent"

18.4 Long-running task crash recovery

Background tasks (run_in_background=true) persist progress to a global on-disk directory (one JSON snapshot per task); reaching a terminal state (done / error / killed) deletes the snapshot.

Item Default Notes
Persistence scope Background tasks only Foreground synchronous tasks aren't persisted
Startup scan Async, non-blocking Dirty-entry cleanup first (entries whose parent session is gone are pruned), then remaining tasks are listed
Auto-resume toggle Off (auto_resume_tasks) Settings → Agent → Reflection; when on, a restart auto-continues (unattended / overnight)
Startup auto-resume cap 5 Prevents a "continue-message storm"; the rest go to the manual banner
Recovery mechanism Inject into parent session Doesn't respawn the sub-process directly; injects a "continue / re-delegate" message into the original parent session (visible to the user)
Manual entry Top recovery banner Each row offers Resume / Ignore, plus "Ignore all"

18.5 Self-repair loop reference

Errors are triaged by origin (request / gateway / upstream) and category, then mapped to 4 repair strategies, each with a per-session attempt cap:

Repair strategy For Cap
transient_compact (compact & retry) Context overflow 2
transient_fallback (switch provider, needs confirm) No channel / upstream overloaded / upstream auth failed 3
backoff (backoff retry) Rate-limit / timeout / network 3
terminal (hand back to user) Insufficient balance / sign in again / model missing —

Special cases: an auth_error of upstream origin is reclassified transient_fallback (a channel key failed, not the user's credentials); a still-retryable terminal is rescued to backoff; 5xx / 408 stay retryable, other 4xx terminate.

  • Network-jitter self-heal: on a transient disconnect / temporary offline, it probes "offline → online" and resumes, and that wait is not counted against the retry budget; connection-level transients (connection reset / momentary DNS failure / interrupted handshake) are sub-classified as network problems (rather than a generic llm_error).
  • Outcome heals (MaxOutcomeHeal = 3, counted consecutively, reset by a clean finish): length-truncated reply auto-continue (truncated_continue), reasoning-loop interruption (reasoning_loop), content-filter rephrase (content_filter), reply stopped mid-sentence (cut_mid_sentence), announced the next step without acting (announced_no_action). The last two are only judged at the turn end on rounds with finish=stop, and not on check-in rounds or rounds with a self-reported pause; mid-sentence is checked first, then the announcement (looking at the last sentence); ending with a question, a hand-back to the user, a polite closing, or a list / table / heading counts as normal. The continuation message has source=auto_self_heal and is invisible; on hitting the cap a toast says "self-repair gave up — {kind} could not be corrected over several turns; manual intervention needed".
  • Gate escalation: when the same check / test failure or a no-progress todo hits its cap (test gate 3×, no-progress todo 2×), the prompt escalates to change approach.
  • Provider fallback is confirmed back via App.RespondProviderFallback; frontend SelfRepairFallbackBanner; backend events self_repair:fallback_confirm / _applied / _declined / self_repair:outcome.

18.6 Experience-learning loop reference

Part Notes
Self-heal history learning (P1) Each heal kind's success/failure persists to ~/.config/avlcode/self-heal-stats.json; smoothed rate (succ+1)/(total+2). Samples < 5 use the default cap 3; rate < 0.2 → 1, 0.2–0.5 → 2, ≥ 0.5 → 3; each kind keeps ≥ 1, history only lowers
Auto-reflect (P2/P3) auto_reflect (default off, Settings → Agent → Reflection); on multi-step task wrap-up runs a GRAI+KISS retrospective async; reads the trace digest (up to 5 error steps) + self-heal stats, producing ≤ 8 Keep/Improve/Stop/Start lessons
Lesson destination Sedimented to the memory palace _pending draft inbox (room scratch, kind decision, never decays); requires human approval before entering the live bank; deduped by title; event reflection:done

18.7 Session routines reference

Separate data from Settings → Routines (workspace routines, §6.13): session routines are created by the model inside a session and, when due, delivered to the session that created them. Stored in ~/.config/avlcode/workspaces/<workspace ID>/session-routines.yaml (not under the project directory's .avlcode/); the scheduler scans open workspaces every 5 seconds.

Tool Parameters Notes
RoutineCreate name (≤ 40 chars), prompt (≤ 4000 chars, must be self-contained), schedule (required type + minutes / at / time / days), when_idle (run / skip), delivery (step_end default / interrupt), max_runs, expires_in_hours Create; calls that arrive after a stop are rejected
RoutineList None This session's unfinished (including paused) routines + recent end records (10 kept per session)
RoutineCancel routine_id (starts with rt_), reason (one line, shown to the user) Cancel; an already-ended one returns cancelled=false
schedule.type Meaning Constraints Default validity / cap Default runs / cap
after Once after N minutes minutes 1–1440; when_idle required — 1 / 1
every Every N minutes minutes 1–720; when_idle required max(8, ⌈3 × minutes / 60⌉) h, capped at 24 / 24 h 30 / 200
at Once at a given time YYYY-MM-DD HH:MM, must be in the future, at most 30 days ahead — 1 / 1
hourly Every hour time uses only the minute (HH:MM / :MM / MM) 72 / 720 h 72 / 720
daily / weekdays Every day / weekdays time is HH:MM 168 / 720 h Unlimited / ≤ 1000 when given explicitly
weekly Every week time + days (0 = Sunday … 6 = Saturday, at least one day) 168 / 720 h Unlimited / ≤ 1000 when given explicitly
Item Value
Availability Main session only; auto / execute have all three tools, plan / prepare / assess only List / Cancel; not available to subagents or the TUI; no global switch, unaffected by tool lazy-loading (always resident)
Tool groups routine.* = all three; agent.* / agent.read.* include only List / Cancel
Quantity caps Unfinished ≤ 5 per session; unfinished ≤ 50 per workspace; ≤ 10 created per session within 24 hours; ≤ 300 deliveries per workspace per calendar day (beyond that: one-shot ones end, repeating ones skip)
Session running when due Inserted into the current loop per delivery; if it can't be inserted, retried every 15 seconds for up to 10 minutes; on timeout one-shot ones end (delivery_failed), repeating ones skip
Session idle when due when_idle=run starts a turn (using the session's most recent work mode and that session's model); skip ends one-shot ones (idle_skipped) and skips repeating ones. When the session is busy (awaiting approval / changing generation / a send in flight / test gate running) or stopped less than 10 seconds ago, it looks again 5 seconds later
Consecutive failures Auto-paused after 3 (a routine-started turn ending in an error counts too); one-shot ones retry after 60 seconds; reset by a normal finish or "Resume"
Missed Not made up: when it re-enters scheduling more than 1 minute late it counts as missed (one-shot ones end with missed, repeating ones recompute from now); resuming after a pause doesn't make up either
Stop The Stop button and IM /stop cancel all of this session's routines (user_stopped); deleting the session clears the records with it; archiving the session ends them with session_archived
Message stream source=auto_routine, shown as a collapsed row "⏰ Routine · name · 3/30"; the delivery footer (not a new user message, grants no new authorisation, should RoutineCancel once done) is shown only to the model; not included in the session catalogue
IM For a turn started by a routine, the reply is forwarded to the IM peer bound to that session (header "⏰ 例行「名称」" — "⏰ Routine 'name'"); no management commands on the IM side

"Routines" strip actions: Pause / Resume, Edit (name, prompt, schedule type, minutes / time / days of week, when the session is idle, when the session is running, max deliveries, expiry; 0 = default), Delete (no second confirmation, leaves no end record); the "Ended" section lists the reason and can be dismissed with "Got it". End reasons: Completed / Reached max deliveries / Expired / Cancelled by the model / You pressed Stop / Session deleted / Session archived / Missed its time / Session was idle; skipped / Delivery failed / Workspace daily delivery limit reached.