Run asynchronous tasks
Preserve ownership, payment admission, and retry safety for long-running fulfillment.
Use a Lucid task when fulfillment should outlive the HTTP request. Installing
a2a() adds owned task state and task routes backed by the canonical
entrypoint registry.
This is Lucid's current task profile, not the official A2A v1 HTTP/JSON-RPC binding. Do not claim cross-vendor A2A conformance; read A2A protocol status.
Configure the task runtime
import { a2a, createInMemoryTaskStore } from '@lucid-agents/a2a';
const taskStore = createInMemoryTaskStore({
maxTasks: 1_000,
retentionMs: 24 * 60 * 60_000,
});
const runtime = await createAgent(meta)
.use(payments({ config }))
.use(
a2a({
tasks: {
store: taskStore,
maxRunMs: 10 * 60_000,
},
})
)
.use(http())
.build();The shipped store is bounded and process-local. Production deployments must
implement the TaskStore port if tasks must survive restart or multiple
workers. The package does not ship SQLite/Postgres task adapters.
Authorization and state
Task creation uses the shared authorization transaction:
request
→ payment/SIWX/policy verification
→ target idempotency and capacity admission
→ durable task reservation + opaque owner-token hash
→ payment settlement/finalization
→ background execution lease
→ completed | failed | cancelledThe public state values are running, completed, failed, and cancelled.
Creation returns a taskId and opaque accessToken; only a hash of the token
belongs in the task store. Possession of that token controls get, list,
subscribe, and cancel operations.
Never place task access tokens in URLs, logs, analytics, browser storage, or model prompts. A missing task and an unauthorized task should not become an existence oracle.
Exactly-once is not promised
TaskStore.claimExecution() provides a fenced lease, and
compareAndSet() rejects stale owners. That prevents many concurrent double
executions, but application side effects can still occur before a worker loses
its lease or crashes.
Make fulfillment idempotent against a business operation ID stored with the task. Use unique database constraints or a downstream idempotency key for external writes. Do not create a second business task simply because a client lost the creation response.
Settlement and failures
For task creation, Lucid settles after the task reservation is durable and before background execution completes. A later failed or cancelled task is not automatically a refund.
Define and publish:
- what counts as accepted versus fulfilled;
- retry and lease-recovery behavior;
- cancellation cutoff and best-effort semantics;
- result retention and deletion;
- refund, credit, or rerun policy after post-settlement failure; and
- how operators reconcile task, payment, and downstream records.
If reservation fails, authorization must abort without settlement. If settlement/recording becomes ambiguous, preserve the reservation and inspect payment evidence before allowing a replacement task.
Operate the workers
- Use a shared atomic store and recover expired
runningleases after process loss. - Size
maxRunMsto terminate genuinely stuck work; it is not a lease heartbeat. - Bound concurrency, input, output, remote calls, and per-task cost.
- Propagate cancellation through the handler's
AbortSignal. - Alert on lease expiry, long-running tasks, repeated failures, store errors, and settled tasks without terminal fulfillment.
- Close the runtime during graceful shutdown and stop accepting new tasks before draining workers.
Test the lifecycle
Test creation without credentials, valid paid creation, wrong access token, duplicate creation retry, concurrent lease claims, cancellation races, timeout, worker crash, expired-lease recovery, stale-owner completion, and retention expiry. Reconcile one paid failed task according to the published customer policy.
See A2A package reference, durable storage, and payment lifecycle.