update_task
Update a task's status atomically, with optional log entry, claim release, knowledge episode, and session-blocking decision.
Instructions
Atomic task state transition. Updates status, optionally appends a log entry, optionally releases the claim. Idempotent on identical inputs. Replaces quick_status + add_log + release_task fan-out.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| task_id | Yes | ||
| plan_id | No | Plan that owns the task (auto-resolved from task if omitted) | |
| status | No | ||
| log_message | No | Optional progress note | |
| log_type | No | Defaults from status: blocked→challenge, others→progress. | |
| release_claim | No | Default: auto (true if status is completed/blocked). Set explicitly to override. | |
| add_learning | No | Optional: also write a knowledge episode (recommended on completion) | |
| session_id | No | Optional work-session id returned by claim_next_task. Uses the agent-loop completion/block endpoint when status is completed or blocked. | |
| decision | No | Optional decision to queue when blocking a session through the agent-loop endpoint. |
Implementation Reference
- src/tools/bdi/intentions.js:297-391 (handler)The updateTaskHandler function: The core handler that executes the 'update_task' tool logic. It performs atomic task state transitions — updates status, optionally appends a log entry, releases the claim (auto if completed/blocked), and optionally writes a knowledge episode. Also supports agent-loop session-based completion/block endpoints.
async function updateTaskHandler(args, apiClient) { const { task_id, status, log_message, add_learning, release_claim, session_id, decision } = args; let planId = args.plan_id; if (session_id && (status === 'completed' || status === 'blocked')) { try { const path = status === 'blocked' ? 'block' : 'complete'; const response = await apiClient.axiosInstance.post(`/agent/work-sessions/${session_id}/${path}`, { summary: log_message, learning: add_learning ? { content: add_learning } : undefined, decision, }); return formatResponse(response.data); } catch { // Fall back to legacy fan-out for older APIs or if the session was not found. } } // Resolve plan_id from task if not provided. if (!planId) { try { const node = await apiClient.axiosInstance.get(`/nodes/${task_id}`).then((r) => r.data); planId = node.plan_id || node.planId; } catch (err) { return errorResponse('not_found', `Could not resolve plan_id from task ${task_id}: ${err.message}`); } } const result = { as_of: asOf(), task_id, plan_id: planId, applied: { status_changed: false, log_added: false, claim_released: false, learning_recorded: false }, failures: [], }; // 1. Status update if (status) { try { await apiClient.nodes.updateNode(planId, task_id, { status }); result.applied.status_changed = true; result.status = status; } catch (err) { result.failures.push({ step: 'update_status', error: err.response?.data?.error || err.message }); } } // 2. Log entry if (log_message) { const logType = args.log_type || STATUS_TO_LOG_TYPE[status] || 'progress'; try { const log = await apiClient.logs.addLog(planId, task_id, { content: log_message, log_type: logType, }); result.applied.log_added = true; result.log_id = log?.id || log?.log?.id; } catch (err) { result.failures.push({ step: 'add_log', error: err.response?.data?.error || err.message }); } } // 3. Claim release — auto if status is terminal, explicit override otherwise. const shouldRelease = typeof release_claim === 'boolean' ? release_claim : status === 'completed' || status === 'blocked'; if (shouldRelease) { try { await apiClient.axiosInstance.delete(`/nodes/${task_id}/claim`); result.applied.claim_released = true; } catch (err) { // Releasing an unclaimed task is not a hard error — just record it. result.failures.push({ step: 'release_claim', error: err.response?.data?.error || err.message }); } } // 4. Optional learning write to knowledge graph. if (add_learning) { try { await apiClient.graphiti.addEpisode({ name: `Task: ${task_id}`, content: add_learning, source: 'task_update', plan_id: planId, node_id: task_id, }); result.applied.learning_recorded = true; } catch (err) { result.failures.push({ step: 'add_learning', error: err.response?.data?.error || err.message }); } } return formatResponse(result); } - src/tools/bdi/intentions.js:252-295 (schema)The updateTaskDefinition object: Defines the tool's name ('update_task'), description, and inputSchema with properties: task_id (required), plan_id, status (enum), log_message, log_type, release_claim, add_learning, session_id, and decision.
const updateTaskDefinition = { name: 'update_task', description: "Atomic task state transition. Updates status, optionally appends a log " + "entry, optionally releases the claim. Idempotent on identical inputs. " + "Replaces quick_status + add_log + release_task fan-out.", inputSchema: { type: 'object', properties: { task_id: { type: 'string' }, plan_id: { type: 'string', description: 'Plan that owns the task (auto-resolved from task if omitted)', }, status: { type: 'string', enum: ['not_started', 'in_progress', 'completed', 'blocked', 'plan_ready'], }, log_message: { type: 'string', description: 'Optional progress note' }, log_type: { type: 'string', enum: ['progress', 'decision', 'blocker', 'completion', 'challenge'], description: "Defaults from status: blocked→challenge, others→progress.", }, release_claim: { type: 'boolean', description: "Default: auto (true if status is completed/blocked). Set explicitly to override.", }, add_learning: { type: 'string', description: 'Optional: also write a knowledge episode (recommended on completion)', }, session_id: { type: 'string', description: 'Optional work-session id returned by claim_next_task. Uses the agent-loop completion/block endpoint when status is completed or blocked.', }, decision: { type: 'object', description: 'Optional decision to queue when blocking a session through the agent-loop endpoint.', }, }, required: ['task_id'], }, }; - src/tools/bdi/intentions.js:297-391 (handler)The STATUS_TO_LOG_TYPE mapping at lines 244-250 maps status values to log types (used by the handler).
async function updateTaskHandler(args, apiClient) { const { task_id, status, log_message, add_learning, release_claim, session_id, decision } = args; let planId = args.plan_id; if (session_id && (status === 'completed' || status === 'blocked')) { try { const path = status === 'blocked' ? 'block' : 'complete'; const response = await apiClient.axiosInstance.post(`/agent/work-sessions/${session_id}/${path}`, { summary: log_message, learning: add_learning ? { content: add_learning } : undefined, decision, }); return formatResponse(response.data); } catch { // Fall back to legacy fan-out for older APIs or if the session was not found. } } // Resolve plan_id from task if not provided. if (!planId) { try { const node = await apiClient.axiosInstance.get(`/nodes/${task_id}`).then((r) => r.data); planId = node.plan_id || node.planId; } catch (err) { return errorResponse('not_found', `Could not resolve plan_id from task ${task_id}: ${err.message}`); } } const result = { as_of: asOf(), task_id, plan_id: planId, applied: { status_changed: false, log_added: false, claim_released: false, learning_recorded: false }, failures: [], }; // 1. Status update if (status) { try { await apiClient.nodes.updateNode(planId, task_id, { status }); result.applied.status_changed = true; result.status = status; } catch (err) { result.failures.push({ step: 'update_status', error: err.response?.data?.error || err.message }); } } // 2. Log entry if (log_message) { const logType = args.log_type || STATUS_TO_LOG_TYPE[status] || 'progress'; try { const log = await apiClient.logs.addLog(planId, task_id, { content: log_message, log_type: logType, }); result.applied.log_added = true; result.log_id = log?.id || log?.log?.id; } catch (err) { result.failures.push({ step: 'add_log', error: err.response?.data?.error || err.message }); } } // 3. Claim release — auto if status is terminal, explicit override otherwise. const shouldRelease = typeof release_claim === 'boolean' ? release_claim : status === 'completed' || status === 'blocked'; if (shouldRelease) { try { await apiClient.axiosInstance.delete(`/nodes/${task_id}/claim`); result.applied.claim_released = true; } catch (err) { // Releasing an unclaimed task is not a hard error — just record it. result.failures.push({ step: 'release_claim', error: err.response?.data?.error || err.message }); } } // 4. Optional learning write to knowledge graph. if (add_learning) { try { await apiClient.graphiti.addEpisode({ name: `Task: ${task_id}`, content: add_learning, source: 'task_update', plan_id: planId, node_id: task_id, }); result.applied.learning_recorded = true; } catch (err) { result.failures.push({ step: 'add_learning', error: err.response?.data?.error || err.message }); } } return formatResponse(result); } - src/tools/bdi/intentions.js:1673-1673 (registration)Registration of update_task handler in the module.exports.handlers map: 'update_task: updateTaskHandler'.
update_task: updateTaskHandler, - src/tools/bdi/intentions.js:1651-1651 (registration)Registration of updateTaskDefinition in the module.exports.definitions array.
updateTaskDefinition,