Tasks API
Tasks are the unit of execution inside Stagent. Each task moves through explicit lifecycle states and can be executed by any configured agent runtime. The Tasks API supports CRUD operations, fire-and-forget execution, real-time log streaming, and workflow-aware sibling queries.
Quick Start
Create a task, queue it, execute it, and stream the logs — a typical integration flow:
// 1. Create a task — starts in "planned" state
interface Task {
id: string;
title: string;
status: string;
priority: number;
projectId?: string;
assignedAgent?: string;
agentProfile?: string;
}
const taskRes: Response = await fetch("http://localhost:3000/api/tasks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: "Analyze Q4 revenue trends",
description: "Review revenue data and produce a summary report with charts",
projectId: "proj-8f3a-4b2c",
priority: 1,
assignedAgent: "claude-code",
agentProfile: "data-analyst",
}),
});
const task: Task = await taskRes.json();
// → { id: "task-9d4e...", status: "planned", ... }
// 2. Queue the task, then execute it
await fetch(`http://localhost:3000/api/tasks/${task.id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: "queued" }),
});
const exec: Response = await fetch(`http://localhost:3000/api/tasks/${task.id}/execute`, { method: "POST" });
// → 202 Accepted — agent is now running in the background
// 3. Stream logs in real time
const logs = new EventSource(`http://localhost:3000/api/tasks/${task.id}/logs`);
logs.onmessage = (e: MessageEvent) => {
const entry: { eventType: string; message: string } = JSON.parse(e.data);
console.log(`[${entry.eventType}] ${entry.message}`);
};
// 4. When done, fetch the result
interface TaskOutput {
taskId: string;
status: string;
result: string;
contentType: string;
}
const outputRes: Response = await fetch(`http://localhost:3000/api/tasks/${task.id}/output`);
const output: TaskOutput = await outputRes.json();
// → { taskId: "task-9d4e...", status: "completed", result: "...", contentType: "markdown" } Base URL
/api/tasks
Endpoints
List Tasks
/api/tasks Retrieve all tasks with optional filtering by project and status. Results are ordered by priority, then newest first.
Query Parameters
| Param | Type | Req | Description |
|---|---|---|---|
| projectId | string | — | Filter tasks by project UUID |
| status | enum | — | Filter by lifecycle state (e.g. running) |
Response 200 — Array of task objects
Task Object
| Field | Type | Req | Description |
|---|---|---|---|
| id | string (UUID) | * | Task identifier |
| title | string | * | Task title |
| description | string | — | Detailed task brief |
| projectId | string (UUID) | — | Associated project |
| status | enum | * | Lifecycle state: planned, queued, running, completed, failed, cancelled |
| priority | number (0–3) | * | 0 = P0 Critical, 3 = P3 Low |
| assignedAgent | string | — | Agent runtime ID |
| agentProfile | string | — | Profile ID for execution |
| createdAt | ISO 8601 | * | Creation timestamp |
| updatedAt | ISO 8601 | * | Last modification timestamp |
Fetch all running tasks for a specific project — useful for building a dashboard or monitoring view:
// Fetch running tasks for a project
const response: Response = await fetch("http://localhost:3000/api/tasks?projectId=proj-8f3a&status=running");
const tasks: Task[] = await response.json();
console.log(`${tasks.length} running tasks`);
tasks.forEach((t: Task) => console.log(` [${t.priority}] ${t.title}`)); Example response:
[
{
"id": "task-9d4e-a1b2",
"title": "Analyze Q4 revenue trends",
"status": "running",
"priority": 1,
"projectId": "proj-8f3a-4b2c",
"assignedAgent": "claude-code",
"agentProfile": "data-analyst",
"createdAt": "2026-04-03T10:30:00.000Z",
"updatedAt": "2026-04-03T10:31:15.000Z"
}
] Create Task
/api/tasks Create a new task. The task starts in planned state. Optionally link documents and assign an agent profile.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| title | string | * | Task title (1–200 characters) |
| description | string | — | Task brief (max 2000 characters) |
| projectId | string (UUID) | — | Project to associate with |
| priority | number | — | Priority level 0–3(default: 2) |
| assignedAgent | enum | — | Agent runtime: claude-code, openai-codex-app-server, anthropic-direct, openai-direct, ollama |
| agentProfile | string | — | Profile ID for agent configuration |
| documentIds | string[] | — | Document UUIDs to attach as context |
Response 201 Created — The created task object with a generated UUID
Create a task with documents attached — the agent will receive these files as context during execution:
// Create a task with attached documents
const response: Response = await fetch("http://localhost:3000/api/tasks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: "Summarize meeting notes",
description: "Extract action items and decisions from the uploaded transcript",
projectId: "proj-8f3a-4b2c",
priority: 1,
assignedAgent: "claude-code",
agentProfile: "technical-writer",
documentIds: ["doc-meeting-transcript-001"],
}),
});
const task: Task = await response.json();
console.log(task.id); // "task-a7c2-..."
console.log(task.status); // "planned" Errors: 400 — Zod validation failure or runtime/profile incompatibility
{
"error": {
"formErrors": [],
"fieldErrors": {
"title": ["String must contain at least 1 character(s)"]
}
}
} Get Task
/api/tasks/{id} Retrieve a single task with resolved relationship names and aggregated usage data from the cost ledger.
Response 200 — Task object with enriched fields
Additional Fields (beyond standard task object)
| Field | Type | Req | Description |
|---|---|---|---|
| projectName | string | — | Resolved project name |
| workflowName | string | — | Parent workflow name (if step task) |
| scheduleName | string | — | Parent schedule name (if scheduled) |
| usage.inputTokens | number | — | Total input tokens consumed |
| usage.outputTokens | number | — | Total output tokens consumed |
| usage.totalTokens | number | — | Combined token count |
| usage.costMicros | number | — | Total cost in microdollars (divide by 1,000,000 for USD) |
| usage.modelId | string | — | Model used for execution |
| usage.startedAt | ISO 8601 | — | Execution start time |
| usage.finishedAt | ISO 8601 | — | Execution end time |
The usage field is only present if the task has execution records in the cost ledger. Use costMicros / 1_000_000 to get USD.
// Fetch task with usage data to check cost
const response: Response = await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2");
const task: Task & { usage?: { costMicros: number; modelId: string; totalTokens: number } } = await response.json();
if (task.usage) {
const costUSD: number = task.usage.costMicros / 1_000_000;
console.log(`Model: ${task.usage.modelId}`);
console.log(`Tokens: ${task.usage.totalTokens.toLocaleString()}`);
console.log(`Cost: $${costUSD.toFixed(4)}`);
} Example response for a completed task:
{
"id": "task-9d4e-a1b2",
"title": "Analyze Q4 revenue trends",
"status": "completed",
"projectName": "Marketing Analysis",
"usage": {
"inputTokens": 12450,
"outputTokens": 3200,
"totalTokens": 15650,
"costMicros": 48200,
"modelId": "claude-sonnet-4-6-20250514",
"startedAt": "2026-04-03T10:31:15.000Z",
"finishedAt": "2026-04-03T10:32:44.000Z"
}
}Errors: 404 — Task not found
Update Task
/api/tasks/{id} Update task fields. Status changes are validated against the transition table. Document bindings can be replaced atomically.
Request Body (all fields optional)
| Field | Type | Req | Description |
|---|---|---|---|
| title | string | — | Updated title (1–200 chars) |
| description | string | — | Updated brief (max 2000 chars) |
| status | enum | — | New lifecycle state (validated against transitions) |
| priority | number | — | Updated priority (0–3) |
| assignedAgent | enum | — | New agent runtime |
| agentProfile | string | — | New profile ID |
| result | string | — | Execution result text |
| documentIds | string[] | — | Replace all document bindings atomically |
Promote a planned task to the queue and escalate its priority:
// Escalate and queue a task for immediate execution
await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: "queued", priority: 0 }),
}); Errors: 400 — Invalid status transition (e.g. planned → completed) or validation failure, 404 — Not found
Delete Task
/api/tasks/{id} Permanently remove a task and its associated data.
Response 200 — { "success": true }
// Permanently remove a task
await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2", { method: "DELETE" }); Errors: 404 — Task not found
Execute Task
/api/tasks/{id}/execute Start agent execution. Returns 202 immediately — the agent runs asynchronously in the background. Task must be in queued status.
Response 202 Accepted — { "message": "Execution started" }
The agent runtime is selected from the task’s assignedAgent field. If no profile is set, Stagent auto-classifies one based on the task description. Budget guardrails are enforced before execution begins.
// Fire-and-forget execution — poll status or stream logs to track progress
const res: Response = await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/execute", {
method: "POST",
});
if (res.status === 202) {
console.log("Agent is running — stream /logs to follow progress");
} else if (res.status === 429) {
console.log("Budget exceeded — increase budget in Settings");
} Errors:
400— Task not inqueuedstate, or runtime/profile incompatibility404— Task not found429— Budget limit exceeded
Resume Task
/api/tasks/{id}/resume Resume a failed or cancelled task from its last session checkpoint. Requires a previous execution session. Maximum 3 resumes per task.
Response 202 Accepted — { "message": "Resume started" }
Resume picks up the agent conversation from the last checkpoint, preserving context. If the resume limit (3) is reached, re-queue the task for a fresh start instead.
// Resume a failed task — the agent continues where it left off
const res: Response = await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/resume", {
method: "POST",
});
if (res.status === 400) {
const err: { error: string } = await res.json();
if (err.error.includes("Resume limit")) {
// Re-queue for fresh execution instead
await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: "queued" }),
});
}
} Errors:
400— No session to resume, resume limit reached (max 3), or task not in failed/cancelled state404— Task not found429— Budget limit exceeded
Cancel Task
/api/tasks/{id}/cancel Cancel a running or queued task. Sends a cancellation signal to the active agent runtime.
Response 200 — { "success": true }
// Cancel a runaway task
await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/cancel", { method: "POST" }); Errors: 404 — Task not found
Respond to Permission Request
/api/tasks/{id}/respond Allow or deny a pending permission request from an executing agent. Optionally save an always-allow rule so the agent won't ask again.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| notificationId | string | * | ID of the pending notification |
| behavior | enum | * | allow or deny |
| message | string | — | Optional message back to the agent |
| updatedInput | object | — | Modified tool input (keys validated against original) |
| alwaysAllow | boolean | — | Save as permanent permission rule |
| permissionPattern | string | — | Glob pattern for the always-allow rule |
Allow a file write permission and remember the decision for future runs:
// Auto-approve file writes to the reports directory
await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/respond", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
notificationId: "notif-456",
behavior: "allow",
alwaysAllow: true,
permissionPattern: "write:/reports/*",
}),
}); Response 200 — { "success": true }
Errors: 400 — Validation failure or disallowed input keys, 404 — Notification not found, 409 — Already responded
Get Task Output
/api/tasks/{id}/output Retrieve the execution result with auto-detected content type. The contentType field tells you how to render it.
Response Body
| Field | Type | Req | Description |
|---|---|---|---|
| taskId | string | * | Task identifier |
| status | string | * | Current task status |
| result | string | * | Execution output text |
| contentType | enum | * | Auto-detected: text, markdown, code, json, or unknown |
// Fetch output and render based on detected content type
const response: Response = await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/output");
const output: { taskId: string; status: string; result: string; contentType: string } = await response.json();
switch (output.contentType) {
case "markdown": renderMarkdown(output.result); break;
case "json": renderJSON(JSON.parse(output.result)); break;
case "code": renderCode(output.result); break;
default: renderPlainText(output.result);
} Example response:
{
"taskId": "task-9d4e-a1b2",
"status": "completed",
"result": "## Q4 Revenue Analysis\n\nRevenue grew 23% QoQ...",
"contentType": "markdown"
}Errors: 404 — Task not found
Stream Task Logs
/api/tasks/{id}/logs Real-time Server-Sent Events stream of agent log entries. Polls every 500ms with 15-second keepalive heartbeats.
Response — text/event-stream with JSON log objects
Each event contains a serialized log entry with eventType, message, and timestamp. The stream stays open until the client disconnects or the task completes.
// Stream agent logs in real time
const source = new EventSource("http://localhost:3000/api/tasks/task-9d4e-a1b2/logs");
source.onmessage = (event: MessageEvent) => {
const log: { eventType: string; message: string; timestamp: string } = JSON.parse(event.data);
const time: string = new Date(log.timestamp).toLocaleTimeString();
console.log(`[${time}] [${log.eventType}] ${log.message}`);
};
source.onerror = () => {
console.log("Stream ended — task may have completed");
source.close();
}; Example stream events:
data: {"eventType":"tool_use","message":"Reading file: src/data/revenue.csv","timestamp":"2026-04-03T10:31:16Z"}
data: {"eventType":"assistant","message":"Analyzing quarterly trends...","timestamp":"2026-04-03T10:31:18Z"}
data: {"eventType":"tool_use","message":"Writing report to /reports/q4-analysis.md","timestamp":"2026-04-03T10:31:22Z"} Get Task Provenance
/api/tasks/{id}/provenance Retrieve the execution provenance graph — shows how the task was created, what triggered it, and its lineage chain.
Response 200 — Provenance graph object
Provenance traces the origin of a task: was it created manually, spawned by a workflow, triggered by a schedule, or promoted from AI Assist?
// Trace where a task came from
const response: Response = await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/provenance");
const prov: { sourceType: string; parentId: string } = await response.json();
console.log(`Source: ${prov.sourceType}`); // "workflow", "schedule", "manual"
console.log(`Parent: ${prov.parentId}`); Errors: 404 — Task not found
Get Sibling Tasks
/api/tasks/{id}/siblings List other tasks in the same workflow run. Returns an empty array if the task is not part of a workflow.
Response Body (Array)
| Field | Type | Req | Description |
|---|---|---|---|
| id | string (UUID) | * | Sibling task ID |
| title | string | * | Task title |
| status | string | * | Current lifecycle state |
| createdAt | ISO 8601 | * | Creation timestamp |
Useful for building workflow progress views — shows all steps alongside the current task:
// Show workflow progress alongside current task
const response: Response = await fetch("http://localhost:3000/api/tasks/task-9d4e-a1b2/siblings");
const siblings: { id: string; title: string; status: string }[] = await response.json();
siblings.forEach((s) => {
const icon: string = s.status === "completed" ? "✓" : s.status === "running" ? "⟳" : "○";
console.log(`${icon} ${s.title} (${s.status})`);
}); Status Transitions
Tasks follow a strict lifecycle. Invalid transitions return 400.
| From | Allowed Targets |
|---|---|
| planned | queued, cancelled |
| queued | running, planned, cancelled |
| running | completed, failed, cancelled |
| completed | planned |
| failed | planned, queued, running |
| cancelled | planned, running |
Error Format
All validation errors follow the Zod flattened format:
{
"error": {
"formErrors": [],
"fieldErrors": {
"title": ["String must contain at least 1 character(s)"],
"priority": ["Number must be less than or equal to 3"]
}
}
}
Constants
| Constant | Value | Description |
|---|---|---|
| MAX_RESUME_COUNT | 3 | Maximum resumes per task |
| DEFAULT_MAX_TURNS | 50 | Default agent conversation turns |
| DEFAULT_MAX_BUDGET_USD | 2.00 | Default per-task budget |
| WORKFLOW_STEP_MAX_BUDGET_USD | 5.00 | Budget cap for workflow step tasks |