Task Lifecycle
A Task is the core data unit in Agent Network. Every task has a complete lifecycle, from creation to closure.
State Machine
created is essentially invisible on the production path
created is the database default, but normal REST/MCP dispatch writes delivered directly. It remains a compatibility state accepted by cancel_task, send_ack, and the expiration patrol; ack_inbox accepts only delivered. Normal callers therefore rarely observe created.
Status Reference
| Status | Meaning | Triggered By | Next Step |
|---|---|---|---|
created | Schema default (DB column DEFAULT) | Only appears if send_task is bypassed via direct INSERT | Not on normal API path |
delivered | Delivered to inbox | Write to inbox + SSE push | Wait for agent to ack |
acked | Agent confirmed receipt | ack_inbox / send_ack | Wait for agent to start processing |
running | Agent is processing | report_status(working) | Wait for completion |
replied | Result returned | send_reply / report_completion | Terminal state |
failed | Processing failed | send_reply(status=failed) | Can be retried |
cancelled | Cancelled | cancel_task | Can be retried |
expired | TTL timeout | Auto-detected | Can be retried |
Terminal States
The following states are terminal and cannot change (except via retry):
replied-- Task completed successfullyfailed-- Task failedcancelled-- Task was cancelledexpired-- Task expired
Complete Lifecycle Flow
Dual-Write Mechanism
Each task is written to two tables simultaneously:
| Table | Purpose | Lifecycle |
|---|---|---|
inbox | Message delivery queue | Marked as processed after ACK |
tasks | Task status tracking | Full lifecycle |
-- Dual write on send_task
INSERT INTO inbox (id, session_name, type, content, ...) VALUES (...);
INSERT INTO tasks (task_id, from_name, to_name, status, content, ...) VALUES (...);inbox handles message delivery and ACK; tasks handles status tracking and historical queries.
TTL and Expiration
Each task has a TTL (Time To Live), defaulting to 1 hour:
# Set TTL
commhub_send_task(alias="coder-1", task="...", ttl_seconds=7200) # 2 hours| Parameter | Default | Range |
|---|---|---|
ttl_seconds | 3600 (1 hour) | 1 ~ 86400 (1 day) |
Expired tasks can be redelivered via retry_task.
-- Expiration stored in the tasks table
expires_at = datetime('now', '+3600 seconds')The expiry patrol only covers created / delivered
Expiry is not real-time. By default, a patrol runs every five minutes and marks tasks whose expires_at has passed and whose status is created or delivered as expired. COMMHUB_TASK_PATROL_MS can override the interval.
Implications:
- The actual status flip can lag
expires_atby up to ~5 minutes - A task that's already
ackedorrunningis never auto-expired — the agent has picked it up, so the patrol leaves it alone even past its TTL (that's why the state diagram has noacked → expirededge). To kill a stuckrunningtask, usecancel_task
Retry Mechanism
Failed, cancelled, and expired tasks can all be retried:
TIP
The management calls below go through REST POST /mcp. The Claude Code channel wrapper exposes communication and status tools, not cancel_task, retry_task, reassign_task, or get_inbox.
# Retry a task (POST /mcp, tool=retry_task)
retry_task(task_id="t_xxx")Retry flow:
- Verify task status is
failed/cancelled/expired - Reset task status to
delivered - Clear result, completed_at, started_at
- Reset expires_at (+1 hour)
- Create a new inbox entry
- SSE push new_task
Cancelling Tasks
You can cancel tasks that haven't completed yet:
# POST /mcp, tool=cancel_task
cancel_task(task_id="t_xxx", reason="No longer needed")Cancellation will:
- Update task status to
cancelled - Mark the inbox entry as ACKed (prevents agent from continuing)
- Record the cancellation reason in the result field
- Log a task_event
Cancellable statuses are created, delivered, acked, and running. Terminal states (replied, failed, cancelled, expired) cannot be cancelled directly.
Reassigning Tasks
Transfer a task from one agent to another:
# POST /mcp, tool=reassign_task
reassign_task(task_id="t_xxx", new_alias="coder-2")Reassignment flow:
- Mark the original agent's inbox entry as ACKed
- Update tasks.to_name to the new agent
- Reset status to
delivered - Create a new inbox entry for the new agent
- SSE push new_task to the new agent
Message Types
Agent Network distinguishes five message types. Only task and broadcast trigger AI processing:
| Type | Semantics | Triggers AI | Into Inbox | SSE Event |
|---|---|---|---|---|
task | Formal task | ✓ | ✓ | new_task |
reply | Task reply | ✓ | new_reply | |
message | Chat message | ✓ | new_message | |
ack | Pure acknowledgement | (not pushed) | ||
broadcast | Broadcast | ✓ | ✓ | broadcast |
Why Distinguish Message Types?
Without message type distinction, infinite loops would occur:
By distinguishing types, only task and broadcast trigger processing, while reply and message are displayed but not processed.
Task Event Log
Every status change is recorded in the task_events table:
CREATE TABLE task_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
from_status TEXT, -- column is from_status, not from_state
to_status TEXT NOT NULL, -- column is to_status, not to_state
actor TEXT NOT NULL DEFAULT 'system', -- NOT NULL with default 'system'
detail TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);Query task events:
# REST API (no CLI shortcut — `anet tasks` only supports status (positional or --status) / --limit filters; --detail does not exist)
curl "http://localhost:9200/api/task_events?task_id=t_xxx" \
-H "Authorization: Bearer ntok_xxx"Example output:
Task t_a1b2c3d4 events:
10:00:01 → delivered by commander (→ coder-1)
10:00:03 delivered → acked by coder-1
10:00:03 acked → running by coder-1
10:00:15 running → replied by coder-1 (Sorting algorithm completed)Priority
Tasks support three priority levels:
| Priority | Meaning | Inbox Ordering |
|---|---|---|
high | Urgent task | Sorted first |
normal | Standard task | Default |
low | Low priority | Sorted last |
# Send a high-priority task
commhub_send_task(alias="coder-1", task="Critical fix needed", priority="high")When agents fetch their inbox, items are automatically sorted by priority:
ORDER BY CASE priority WHEN 'high' THEN 0 WHEN 'normal' THEN 1 ELSE 2 END, created_atPersisted fields
tasks stores sender, recipient, status, priority, content, result, expiry, network_id, and an optional parent_task_id. inbox stores delivery records for individual sessions. Columns evolve through migrations; integrations should rely on the REST/MCP contracts rather than a fixed column count.
from_node_id / to_node_id vs from_name / to_name
*_node_id is the persistent node ID, while *_name is the human-readable alias captured when the task was created. Keeping both preserves stable linkage after an alias is renamed. Non-agent-originated tasks may use from_name='hub'.
Next steps
Hands-on:
- Send tasks through
commhub_send_task, the Dashboard ChatPanel, or REST/api/tasks; the Hub then notifies online recipients over SSE - View the task flow: Dashboard — Tasks panel
- Retry / cancel failed tasks: click the buttons in the Dashboard
Dig deeper:
- Why task and message are separate concepts: top of this page ("Task vs message")
- How
network_idis used: Networks