A3S Docs
A3S CodeExamples

External Tasks

Fulfill agent-queued work from outside the agent process and report structured evidence back

External Tasks

Some work cannot run inside the agent process: it belongs to a separate worker, a CI runner, or a human in another system. When a lane is routed to an external handler, the tools on that lane are queued instead of executed — they wait as external tasks. Your host code drains the pending queue, does the work however it likes, and reports the outcome back with completeExternalTask. Reach for this only when an outside worker is genuinely part of your architecture.

External tasks are produced by the lane queue: you must register at least one external (or hybrid) lane handler first, otherwise every task runs in-process and there is nothing to drain.

import { Agent } from '@a3s-lab/code';

const agent = await Agent.create('agent.acl');
const session = agent.session(process.cwd());

// Route a lane to an external worker so its tools are queued, not executed.
await session.setLaneHandler('execute', { mode: 'external', timeoutMs: 300000 });

// Drain the tasks waiting for the host to fulfill.
const pending = await session.pendingExternalTasks();

for (const task of pending) {
  console.log(`pending: ${task.task_id} on ${task.lane} (${task.command_type})`);

  try {
    // ...the host does the real work here (run CI, call a service, ask a human)...
    const ok = await session.completeExternalTask(task.task_id, {
      success: true,
      result: {
        summary: 'worker completed the test run',
        command: 'npm run build',
        exitCode: 0,
      },
    });
    console.log('completed:', ok);
  } catch (err) {
    await session.completeExternalTask(task.task_id, {
      success: false,
      error: String(err),
    });
  }
}

await session.close();
import os
from a3s_code import Agent

agent = Agent.create(open("agent.acl").read())
session = agent.session(os.getcwd())

# Route a lane to an external worker so its tools are queued, not executed.
session.set_lane_handler("execute", "external", 300000)

# Drain the tasks waiting for the host to fulfill.
pending = session.pending_external_tasks()

for task in pending:
    print(f"pending: {task['task_id']} on {task['lane']} ({task['command_type']})")

    try:
        # ...the host does the real work here (run CI, call a service, ask a human)...
        ok = session.complete_external_task(
            task["task_id"],
            success=True,
            result={
                "summary": "worker completed the test run",
                "command": "npm run build",
                "exit_code": 0,
            },
        )
        print("completed:", ok)
    except Exception as err:
        session.complete_external_task(
            task["task_id"],
            success=False,
            error=str(err),
        )

session.close()

Notes:

  • Each pending task carries task_id, session_id, lane, command_type, payload, and timeout_ms. Pass the task_id back to completeExternalTask / complete_external_task to match the completion to the right task.
  • The result shape is { success, result?, error? }. result holds any JSON-serializable payload; error is an optional message for failures.
  • On success, return compact structured evidence (a summary plus the key facts), not raw logs only — the agent reasons over the result, so keep it small and machine-readable.
  • completeExternalTask / complete_external_task returns true when the task was found and completed, false otherwise. In Python these queue methods are synchronous; in Node pendingExternalTasks and completeExternalTask return promises.

On this page