On this page

Tasks track deferred and asynchronous tool executions. When a tool call requires approval (HIL via restricted) or is executed asynchronously, the runtime creates a task entry in adf_tasks that records the tool name, arguments, status, and eventual result.

When Tasks Are Created

Tasks are created in two scenarios:

1. Restricted Tools from the LLM Loop (HIL)

Tools configured with enabled: true and restricted: true create a task with pending_approval status and requires_authorization: true when called from the LLM loop. The agent’s turn blocks until the task is resolved (approved or denied).

The task can be resolved by:

  • UI approval dialog / HTTP — the owner clicks approve/deny in the Studio UI, or a client calls the daemon endpoint POST /agents/:id/tasks/:taskId/resolve
  • on_task_create trigger lambda — dispatches to an external approval system (Telegram, multi-agent vote, etc.) which calls task_resolve
  • task_resolve from authorized code — any authorized lambda can approve/deny

The resolve endpoint returns 404 for an unknown task id and 409 when the task is not in a resolvable status — only pending and pending_approval rows can be resolved. A task already swept to a terminal status by orphan reconciliation therefore returns 409.

When a blocking (synchronous) HIL task is denied, on_task_complete is deliberately not fired — the agent already receives the rejection in-band as the tool-call error result, so a trigger would be redundant. Denials of async (_async: true) HIL tasks do fire on_task_complete, since the agent has no inline context for those.

{
  "tools": [
    { "name": "fs_write", "enabled": true, "restricted": true }
  ]
}

restricted is owner-set only — an agent’s sys_update_config call touching it is hard-denied (no HIL prompt). See Authorized Code for the security model.

Async HIL: If the agent calls a restricted tool with _async: true, the task is created but the agent continues without waiting. The task reference is returned immediately:

{ "task_id": "task_abc123", "status": "pending_approval", "tool": "fs_write" }

2. Async Execution (_async: true)

Any tool call can be made asynchronous by including _async: true in the input. The runtime strips the flag, creates a task in running status, and executes the tool in the background:

{ "task_id": "task_def456", "status": "running", "tool": "sys_code" }

The LLM can continue its turn without waiting. The task updates to completed or failed when execution finishes.

Task Statuses

StatusDescription
pendingCreated, not yet executing
pending_approvalAwaiting human/authorized approval (HIL)
runningCurrently executing
completedFinished successfully
failedFinished with an error
deniedApproval rejected
cancelledCancelled before completion

Terminal statuses (completed, failed, denied, cancelled) record a completed_at timestamp.

Orphaned Task Reconciliation (on load)

A crash or hard shutdown can leave adf_tasks rows stranded in a non-terminal state. On load — before any turn of the new session runs — the executor sweeps these orphans (every row it sees predates the load, so nothing legitimately in flight is affected):

Orphaned rowSwept toWhy
runningfailed (error: “outcome unknown”)The tool may have produced side effects before the process died; the outcome cannot be known.
pending_approval, executor-managedcancelledThese are the executor’s own blocking HIL approvals — nobody is left waiting on them. Not denied: no human ever decided.
pending_approval, not executor-managedleft openOwned by lambdas/UI, which can still resolve them after restart.

Each swept executor-managed approval emits hil.resolved { orphaned: true, approved: false } (closing the one-resolve-per-request guarantee for a request whose emitter died). If any rows were swept, the executor emits a single loop.recovered { reason: 'orphaned_tasks', running, awaiting_approval }. Reconciliation is diagnostic — a failure never blocks agent load.

Task Schema

The adf_tasks table:

ColumnTypeDescription
idTEXT PRIMARY KEYUnique identifier (task_ + nanoid)
toolTEXTTool name (e.g., fs_write)
argsTEXTJSON-stringified tool arguments
statusTEXTCurrent status (see above)
resultTEXTJSON result on success, NULL otherwise
errorTEXTError message on failure, NULL otherwise
created_atINTEGERUnix timestamp (ms)
completed_atINTEGERUnix timestamp (ms), set on terminal status
originTEXTSource — hil:AgentName:id for HIL, agent:AgentName:id for async
requires_authorizationINTEGER1 if only authorized code can approve/deny this task
executor_managedINTEGER1 if the executor is waiting to execute the tool after approval

Task-Level Authorization

HIL tasks are created with requires_authorization: true, meaning only authorized code (or the UI dialog, which is owner-authorized) can approve or deny them. This prevents the agent from self-approving its own gated tool calls.

For non-HIL tasks, requires_authorization can be set via task_resolve:

await adf.task_resolve({
  task_id: taskId,
  action: "pending_approval",
  requires_authorization: true
});

Once set, the flag cannot be unset.

Code Execution and Restricted Tools

Restricted tools (restricted: true) can only be called freely from authorized code. Unauthorized code is always blocked.

Code ContextRestricted tool
sys_code (always unauthorized)Blocked
sys_lambda from loop → unauthorized targetBlocked
sys_lambda from loop → authorized targetHIL — approved → Allowed
sys_lambda from authorized file (authorized)Allowed
Trigger/timer lambda from authorized fileAllowed

When the LLM calls sys_lambda targeting an authorized file, the runtime triggers a HIL approval prompt. If approved, the lambda runs with authorization and can call restricted tools. This is the same approval mechanism used for restricted tool calls from the loop. Authorized lambdas called from code or triggers run without prompting.

Hiding a tool from the LLM does not make it inaccessible to code. Set visible: false to keep an enabled tool callable from code (and lambdas) while removing it from the LLM’s tool schema. A tool with enabled: false, restricted: true can still be called from authorized code — it is just hidden from the LLM loop.

Querying Tasks

Use db_query to inspect tasks. Note that the on_task_create trigger below is disabled by default — enable via triggers.on_task_create.enabled.

-- Recent tasks
SELECT * FROM adf_tasks ORDER BY created_at DESC LIMIT 20

-- Pending approval tasks
SELECT * FROM adf_tasks WHERE status = 'pending_approval'

-- Failed tasks for a specific tool
SELECT * FROM adf_tasks WHERE tool = 'fs_write' AND status = 'failed'

Task Triggers

on_task_create

Fires when a task is created. This is the hook for external approval routing — the lambda receives the full task details and can dispatch approval requests.

{
  "on_task_create": {
    "enabled": true,
    "targets": [{
      "scope": "system",
      "lambda": "lib/hil/dispatcher.ts:onTaskCreate",
      "filter": { "tools": ["*"] }
    }]
  }
}

Example lambda:

export async function onTaskCreate(event) {
  const { task } = event.data;
  if (!task.requires_authorization) return;

  await adf.msg_send({
    recipient: "telegram:123456789",
    content: `Approval needed: ${task.tool}\nArgs: ${task.args}`,
    subject: `task:${task.id}`
  });
}

on_task_complete

Fires when a task reaches a terminal status (completed, failed, denied, cancelled). Filter by tool name and/or status:

{
  "on_task_complete": {
    "enabled": true,
    "targets": [{
      "scope": "agent",
      "filter": { "tools": ["fs_write", "msg_send"], "status": "completed" }
    }]
  }
}

See Triggers for the full trigger system.

Tool Side Effects Through Task Resolution

When task_resolve approves a task, the tool executes and its side effects are propagated. For HIL tasks (executor-managed), the executor runs the tool in its own context — preserving endTurn handling, file diffs, and state transitions. For deferred tasks, task_resolve executes the tool in the call handler context.

UI

The Tasks tab in the Bottom Panel displays all tasks with status filtering, expandable argument/result details, auto-refresh, and an AUTH badge for tasks requiring authorized code.