harnest.task API reference for parameters, return types, and methods.
Configure Lifecycle storage
Before queueing work, register a task storage provider in the agent’s rootlifecycle/ folder. Adding a @task function does not configure storage. Harnest discovers these Python factories automatically; there is no task-storage setting to add to config.yaml.
For the durable tool example below, create or update lifecycle/storage.py:
lifecycle/storage.py
DATABASE_URL to your running PostgreSQL database before starting the agent. harnest_postgres ships with the managed Harnest runtime. Harnest calls the factory and owns starting and closing its provider.
Replace any existing factories for the roles shown above; do not register a second provider for the same role. If you already have session and checkpoint providers that you want to keep, register only @lifecycle.storage.tasks on this new factory.
For local experiments, a task factory can return
harnest.task.MemoryTaskStore() instead. It loses queued work on process exit and does not provide session or checkpoint storage; register it only for task and optional cron roles. Use persistent storage for production. See Task and cron storage for Redis, custom providers, and complete storage setup.
Define and call a task
From the agent root, create a compilable task starter:--project <agent-root> when running the command from elsewhere. The generated file is suitable when no other module imports the task; use the shared-library pattern below when tools also import it.
When a tool needs the task callable, define it once in lib/ and re-export it from tasks/. The following three files work together:
1
Define the shared task
lib/report_tasks.py
2
Make the task discoverable
tasks/build_report.py
3
Queue work from an Agent Tool
tools/request_report.py
tasks/<name>.py. The file must export exactly one same-named @task callable.
Tasks can be synchronous or asynchronous. Give each task a docstring and accept JSON-safe arguments; its result must also be JSON-safe. See the task parameter contracts for signature restrictions.
Decide whether to wait
For fire-and-forget work, defer the task and return its ID to the caller. Wait for its result when the agent needs the output to continue. An unfinished result requires an async@tool(durable=True) and a Harnest-owned checkpointer.
The returned TaskHandle lets managed code inspect status, cancel work, and access its result. It belongs to the active compiled runtime; there is no public API to reconstruct or list handles by ID.
Make retries safe
Workers can retry a task. Make side effects idempotent, such as recording a report under a stable business key before notifying another service. An idempotency key deduplicates submission; it does not make every external effect happen exactly once. Inside a durable tool, Harnest derives a replay-stable submission key when you omit one. Usedefer(schedule_in=...) for a one-time delay. For recurring work, use Cron schedules.
Runtime requirements
With Lifecycle storage configured and
DATABASE_URL set, run from your agent folder. Replicas can share the same persistent provider:
.defer() outside it raises TaskUnavailableError. Call the task body directly for database-free unit tests.
Start a fresh agent session
A task can invoke the compiled root agent without an HTTP call throughcontext.agent.create_session() and AgentSession.invoke(). Pass a stable session key when retries should derive the same child session and invocation identities.
To continue an existing conversation, use context.agent.open_session(session_id) inside the task. It opens only sessions owned by the task’s user, including the session that queued the task. Choose the destination explicitly; defer() does not select one. For example, a one-time reminder can carry the current session ID into a delayed task:
.defer() runs under an Agent Runtime Principal, Harnest stores only its permission names and reconstructs a fresh principal for each worker attempt. Agent calls made by the Task inherit that restriction unless trusted task code passes a narrower principal. A Task deferred without an active principal preserves compatibility behavior.
Durable external waits return a typed in_progress response. Human approvals and client tools fail closed because their continuation is process-local after the task returns.
See Durable execution for cross-replica resume behavior.
Test Task behavior
Call the decorated function directly to unit-test its business logic without a database. Exercise.defer(), retries, cancellation, and durable resume through a compiled smoke test with your chosen persistent provider so the real queue and checkpoint boundaries run together. Custom providers can use the shared conformance suite.