update_goal
Atomically update a goal's title, description, priority, status, type, success criteria, promotion to intention, linked plans, and achievers in a single request.
Instructions
Atomic goal update. Subsumes update_goal + link_plan_to_goal + unlink_plan_from_goal + add_achiever + remove_achiever. All changes apply together.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| goal_id | Yes | ||
| changes | Yes |
Implementation Reference
- src/tools/bdi/desires.js:105-156 (handler)The main handler function for the update_goal tool. Receives goal_id and changes, applies direct field updates (title, description, priority, status, success_criteria, goal_type, promote_to_intention), then processes add/remove linked plans and add/remove achievers. Returns a summary of applied changes, failures, and the updated goal.
async function updateGoalHandler(args, apiClient) { const { goal_id, changes } = args; const applied = []; const failures = []; // Direct field updates const directFields = {}; for (const k of ['title', 'description', 'priority', 'status', 'success_criteria']) { if (changes[k] !== undefined) directFields[k] = changes[k]; } if (changes.goal_type) directFields.goalType = changes.goal_type; if (changes.promote_to_intention) directFields.goalType = 'intention'; if (Object.keys(directFields).length) { try { await apiClient.goals.update(goal_id, directFields); applied.push('direct_fields'); } catch (err) { failures.push({ step: 'direct_fields', error: err.message }); } } for (const planId of safeArray(changes.add_linked_plans)) { try { await apiClient.goals.linkPlan(goal_id, planId); applied.push(`link_plan:${planId}`); } catch (err) { failures.push({ step: `link_plan:${planId}`, error: err.message }); } } for (const planId of safeArray(changes.remove_linked_plans)) { try { await apiClient.goals.unlinkPlan(goal_id, planId); applied.push(`unlink_plan:${planId}`); } catch (err) { failures.push({ step: `unlink_plan:${planId}`, error: err.message }); } } for (const nodeId of safeArray(changes.add_achievers)) { try { await apiClient.goals.addAchiever(goal_id, nodeId); applied.push(`add_achiever:${nodeId}`); } catch (err) { failures.push({ step: `add_achiever:${nodeId}`, error: err.message }); } } for (const nodeId of safeArray(changes.remove_achievers)) { try { const achievers = await apiClient.goals.listAchievers(goal_id); const link = safeArray(achievers.achievers || achievers).find((a) => a.source_node_id === nodeId); if (link) { await apiClient.goals.removeAchiever(goal_id, link.id); applied.push(`remove_achiever:${nodeId}`); } } catch (err) { failures.push({ step: `remove_achiever:${nodeId}`, error: err.message }); } } let goal = null; try { goal = await apiClient.goals.get(goal_id); } catch {} return formatResponse({ as_of: asOf(), goal_id, applied_changes: applied, failures, goal }); } - src/tools/bdi/desires.js:75-103 (schema)The input schema definition for update_goal tool. Defines required fields: goal_id (string) and changes (object) containing optional fields like title, description, priority, status, goal_type, success_criteria, promote_to_intention, add_linked_plans, remove_linked_plans, add_achievers, remove_achievers.
const updateGoalDefinition = { name: 'update_goal', description: "Atomic goal update. Subsumes update_goal + link_plan_to_goal + unlink_plan_from_goal " + "+ add_achiever + remove_achiever. All changes apply together.", inputSchema: { type: 'object', properties: { goal_id: { type: 'string' }, changes: { type: 'object', properties: { title: { type: 'string' }, description: { type: 'string' }, priority: { type: 'integer' }, status: { type: 'string' }, goal_type: { type: 'string', enum: ['desire', 'intention'] }, success_criteria: {}, promote_to_intention: { type: 'boolean' }, add_linked_plans: { type: 'array', items: { type: 'string' } }, remove_linked_plans: { type: 'array', items: { type: 'string' } }, add_achievers: { type: 'array', items: { type: 'string' } }, remove_achievers: { type: 'array', items: { type: 'string' } }, }, }, }, required: ['goal_id', 'changes'], }, }; - src/tools/bdi/desires.js:257-264 (registration)Tool registration exports from desires.js. The updateGoalDefinition is included in the definitions array and updateGoalHandler is mapped to 'update_goal' in the handlers object, which is consumed by bdi/index.js and ultimately by src/tools.js (setupTools).
module.exports = { definitions: [listGoalsDefinition, updateGoalDefinition, deriveSubgoalDefinition], handlers: { list_goals: listGoalsHandler, update_goal: updateGoalHandler, derive_subgoal: deriveSubgoalHandler, }, }; - src/tools.js:19-65 (registration)Top-level MCP tool setup. The setupTools function wires BDI tool definitions and handlers into an MCP server using ListToolsRequestSchema and CallToolRequestSchema. This is where update_goal gets registered with the MCP server.
function setupTools(server, apiClientOverride) { const apiClient = apiClientOverride || defaultApiClient; if (process.env.NODE_ENV === 'development') { console.error(`Setting up MCP tools (${bdiToolDefinitions.length} BDI tools)`); } server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: bdiToolDefinitions }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (process.env.NODE_ENV === 'development') { console.error(`Calling tool: ${name}`); } if (!bdiToolNames.has(name)) { return { isError: true, content: [{ type: 'text', text: `Unknown tool: ${name}. v0.9.0 ships 15 BDI tools. Run get_started to see them, or check ../docs/MIGRATION_v0.9.md for the legacy → BDI mapping.`, }], }; } try { return await bdiToolHandler(name, args, apiClient); } catch (err) { if (process.env.NODE_ENV === 'development') { console.error(`Tool ${name} threw:`, err); } return { isError: true, content: [{ type: 'text', text: `Tool ${name} failed: ${err.message || String(err)}`, }], }; } }); } module.exports = { setupTools }; - src/tools/bdi/_shared.js:1-29 (helper)Shared helpers used by updateGoalHandler: asOf() for timestamps, formatResponse() for MCP response formatting, errorResponse() for error formatting, and safeArray() for safely iterating arrays of linked plans and achievers.
/** * Shared helpers for BDI tool implementations. */ function asOf() { return new Date().toISOString(); } function formatResponse(data) { if (data && data.error) { return { isError: true, content: [{ type: 'text', text: data.error }], }; } return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], }; } function errorResponse(error_type, message, extra = {}) { return formatResponse({ error: message, error_type, ...extra }); } function safeArray(value) { return Array.isArray(value) ? value : []; } module.exports = { asOf, formatResponse, errorResponse, safeArray };