Use tasks when work needs a durable lifecycle, not just a one-shot message: a status that changes over time, a priority, an assignee, comments, tags, attachments, and an audit trail. Start with TaskClient; Hono apps can use c.var.task after installing the Agentuity middleware.
npm install @agentuity/taskimport { TaskClient } from '@agentuity/task';
import type { UserEntityRef } from '@agentuity/task';
const tasks = new TaskClient();
const reporter = {
id: 'agent_triage',
name: 'Triage Agent',
type: 'agent',
} satisfies UserEntityRef;
export async function fileBug(errorMessage: string, traceId: string): Promise<string> {
const task = await tasks.create({
title: 'Payment flow error',
type: 'bug',
priority: 'high',
description: errorMessage,
created_id: reporter.id,
creator: reporter,
metadata: { traceId, source: 'checkout' },
});
return task.id;
}TaskClient reads AGENTUITY_SDK_KEY, then AGENTUITY_CLI_KEY, from the environment. Keep that key in .env for local development and configure the same variable for deployed apps.
When to use tasks
| Need | Use |
|---|---|
| work items with status, priority, comments, and audit history | Tasks |
| fire-and-forget async handoff | Queues |
| recurring timed delivery | Schedules |
| inbound HTTP events with receipts | Webhooks |
| exact key lookup or counters | Key-Value Storage |
Client Setup
Construct the client once at module scope and reuse it from handlers, routes, or scripts.
import { TaskClient } from '@agentuity/task';
const tasks = new TaskClient({
orgId: process.env.AGENTUITY_CLOUD_ORG_ID,
});| Option | Description |
|---|---|
apiKey | Optional API key. Defaults to AGENTUITY_SDK_KEY, then AGENTUITY_CLI_KEY. |
orgId | Optional organization ID. Used when the API key is org-scoped or when calling from a CLI context. |
url | Optional Task API URL. Defaults to AGENTUITY_TASK_URL, then the regional Agentuity service URL. |
logger | Optional logger instance. |
Create Tasks
create() requires title, type, and created_id. Pass creator so dashboards and history can show a display name and whether the actor is a human or an agent.
const reviewer = {
id: 'user_123',
name: 'Maya Chen',
type: 'human',
} satisfies UserEntityRef;
const task = await tasks.create({
title: 'Review suspicious refund',
type: 'task',
priority: 'medium',
created_id: reporter.id,
creator: reporter,
assignee: reviewer,
metadata: {
refundId: 'rf_123',
riskScore: 0.91,
},
});| Field | Required | Description |
|---|---|---|
title | Yes | Up to 1024 characters. |
type | Yes | One of epic, feature, enhancement, bug, task. |
created_id | Yes | ID of the creating user, service, or agent. |
creator | No | { id, name, type? } reference for readable attribution. |
description | No | Up to 65,536 characters. |
priority | No | high, medium, low, or none. Defaults to none. |
status | No | Initial status. Defaults to open. |
assignee | No | { id, name, type? } reference for the assigned actor. |
parent_id | No | Parent task ID for epics, features, or subtasks. |
tag_ids | No | Existing tag IDs to attach at creation. |
metadata | No | JSON metadata for trace IDs, integration IDs, or custom fields. |
project | No | { id, name } project reference. |
Update Lifecycle
Tasks use four canonical statuses. The client also accepts started, completed, and closed, and normalizes them server-side.
| Status | Meaning |
|---|---|
open | Created, not started. |
in_progress | Actively being worked on. |
done | Work completed. |
cancelled | Work abandoned. |
const claimed = await tasks.update(task.id, {
status: 'in_progress',
assignee: reporter,
});
const closed = await tasks.close(claimed.id);Use close(id) for completed work. Use update(id, { status: 'cancelled' }) when the task should be abandoned.
Comments and Tags
Comments take the task ID, body, and author user ID. Pass author so the comment lists carry a display name and type.
await tasks.createComment(
task.id,
'Refund matched the manual review policy.',
reporter.id,
reporter
);
const { comments } = await tasks.listComments(task.id, { limit: 20, offset: 0 });
const tag = await tasks.createTag('payments', '#00FFFF');
await tasks.addTagToTask(task.id, tag.id);
const tagsForTask = await tasks.listTagsForTask(task.id);listTags() returns the bare array. listTagsForTask() returns the tags currently attached to one task.
Attachments
Attachments use a two-step upload. Ask the task service for a presigned upload URL, PUT the bytes to that URL, then confirm the attachment so it shows up in listAttachments().
const { attachment, presigned_url } = await tasks.uploadAttachment(task.id, {
filename: 'refund-log.json',
content_type: 'application/json',
size: 1024,
});
const upload = await fetch(presigned_url, {
method: 'PUT',
body: JSON.stringify({ refundId: 'rf_123' }),
headers: { 'Content-Type': 'application/json' },
});
if (!upload.ok) {
throw new Error(`Attachment upload failed (${upload.status})`);
}
const confirmed = await tasks.confirmAttachment(attachment.id);
const { presigned_url: downloadUrl, expiry_seconds } = await tasks.downloadAttachment(confirmed.id);Presigned URLs expire after expiry_seconds. Request a fresh URL when a user needs to upload or read the file again.
File a Task from a Server Route
Filing a task is a typical pattern when an agent or background worker spots something that needs human follow-up. The route validates the payload, calls tasks.create(), and returns the new task ID.
import { Hono } from 'hono';
import { agentuity } from '@agentuity/hono';
import type { Services } from '@agentuity/hono';
type Variables = Pick<Services, 'task'>;
const app = new Hono<{ Variables: Variables }>();
app.use('*', agentuity());
app.post('/tasks/refund-review', async (c) => {
const body = await c.req.json<{
refundId: string;
note: string;
reporter: { id: string; name: string };
}>();
const task = await c.var.task.create({
title: `Review refund ${body.refundId}`,
type: 'task',
priority: 'medium',
description: body.note,
created_id: body.reporter.id,
creator: { id: body.reporter.id, name: body.reporter.name, type: 'human' },
metadata: { refundId: body.refundId },
});
return c.json({ taskId: task.id }, 201);
});
app.get('/tasks/:id', async (c) => {
const task = await c.var.task.get(c.req.param('id'));
return task ? c.json({ task }) : c.json({ error: 'not found' }, 404);
});
export default app;List and Inspect
Use list() for filtered views and changelog() when you need the field-level history for one task.
const { tasks: openBugs, total } = await tasks.list({
type: 'bug',
status: 'open',
priority: 'high',
include: ['metadata', 'tags'],
sort: '-created_at',
limit: 25,
offset: 0,
});
const { changelog } = await tasks.changelog(task.id, { limit: 50, offset: 0 });| Filter | Description |
|---|---|
status, type, priority | Filter by canonical lifecycle, classification, or priority. |
assigned_id | Filter by assigned actor ID. |
created_id | Filter by creator ID. |
parent_id | Return subtasks for a parent task. |
project_id | Filter by project ID. |
tag_id | Filter by tag ID. |
include | Add fields to the summary response: description, metadata, tags, subtask_count, created_id, deleted. |
sort | Sort by created_at, updated_at, or priority. Prefix with - for descending. |
order | Optional sort direction, asc or desc. |
limit / offset | Pagination controls. |
deleted | Include soft-deleted tasks. |
list() returns a reduced summary by default. Use include when a dashboard needs description, tags, or subtask counts in the same call.
Soft Delete Tasks
softDelete() removes a task from normal work views without erasing its history.
const deleted = await tasks.softDelete(task.id);
const { tasks: deletedTasks } = await tasks.list({
deleted: true,
include: ['deleted'],
});Use close() or update(id, { status: 'cancelled' }) for lifecycle changes. Use softDelete() for cleanup or admin flows where the task should no longer appear in default lists.
Activity Counts
getActivity() returns daily counts grouped by canonical status.
const activity = await tasks.getActivity({ days: 30 });days is optional and accepts values between 7 and 365. The service applies its own default when days is omitted, so pass an explicit value if dashboards depend on a specific range.
Hono Middleware Variant
@agentuity/hono builds TaskClient once and exposes it on c.var.task.
npm install @agentuity/hono honoimport { Hono } from 'hono';
import { agentuity } from '@agentuity/hono';
import type { Services } from '@agentuity/hono';
type Variables = Pick<Services, 'task'>;
const app = new Hono<{ Variables: Variables }>();
app.use('*', agentuity());
app.post('/tasks', async (c) => {
const body = await c.req.json<{ title: string; userId: string; userName: string }>();
const task = await c.var.task.create({
title: body.title,
type: 'task',
created_id: body.userId,
creator: { id: body.userId, name: body.userName, type: 'human' },
});
return c.json({ taskId: task.id }, 201);
});
app.get('/tasks/:id', async (c) => {
const task = await c.var.task.get(c.req.param('id'));
return task ? c.json({ task }) : c.json({ error: 'Task not found' }, 404);
});
export default app;Next Steps
- Queues: hand task-related work to background processors
- Schedules: create recurring checks that file or update tasks
- Webhooks: turn external events into tasks
- Using Standalone Packages: configure service clients outside Agentuity projects
- Tasks API Reference: inspect REST fields and lower-level method details