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 |
| 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.