batondeck
v0.1.0https://mcp.batondeck.com/mcpAdd to your MCP client configuration:
Tools
create_project
Create a project (workspace). The caller becomes its admin. The project is stamped with the caller's active organization (when the token carries one), which scopes where it appears in list_projects.
Parameters
namestringrequiredargumentidempotencyKeystringargumentReturns
projectIdstringrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "create_project",
"arguments": {
"name": "string",
"idempotencyKey": "string"
}
}
}const result = await client.callTool("create_project", {
"name": "string",
"idempotencyKey": "string"
});result = await session.call_tool("create_project", arguments={
"name": "string",
"idempotencyKey": "string"
}){
"projectId": "string"
}list_projects
List the projects the caller is a member of (the discovery entry point), or — with all:true, workspace admins only — every project in the workspace, unfiltered. The member list is membership-driven AND org-scoped: when the caller's token carries an active organization, it returns only that organization's projects plus legacy projects with no organization stamp; switching the active org switches which projects are visible. A caller with no active organization (dev mode, org API key) sees all their memberships. Direct per-project access (get_project by id) is org-agnostic and unaffected by this filter. Rows carry each project's spend cap (quotaEnabled/txnCap) so the operator who may SET a cap can also read it back.
Parameters
allbooleanargumentReturns
projectsArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_projects",
"arguments": {
"all": true
}
}
}const result = await client.callTool("list_projects", {
"all": true
});result = await session.call_tool("list_projects", arguments={
"all": true
}){
"projects": [
{
"projectId": "string",
"name": "string",
"role": "viewer",
"owner": "string",
"quotaEnabled": true,
"txnCap": 0,
"ownerDisplay": "string",
"members": {},
"memberDisplay": {},
"color": "string"
}
]
}get_project
Get a project including members and settings. memberDisplay maps each member id to something a human recognises (their name, else their email); it is DERIVED at read, not stored, and a member absent from it simply has no better label than its id — render the id.
Parameters
projectIdstringrequiredargumentReturns
projectobjectrequiredmemberDisplayobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_project",
"arguments": {
"projectId": "string"
}
}
}const result = await client.callTool("get_project", {
"projectId": "string"
});result = await session.call_tool("get_project", arguments={
"projectId": "string"
}){
"project": {
"id": "string",
"name": "string",
"createdBy": "string",
"createdAt": "string",
"tenantId": "string",
"members": {},
"grants": {},
"settings": {
"quotaEnabled": true,
"txnCap": 0,
"reviewEnabled": true,
"transitionTable": {},
"selectionWeights": {
"priority": 0,
"deadline": 0,
"readiness": 0,
"capability": 0,
"aging": 0
},
"agingCeilingMs": 0,
"capabilityAliases": {},
"retryPolicy": {
"baseDelayMs": 0,
"maxDelayMs": 0,
"jitter": 0,
"poisonAfterAttempts": 0
},
"anomalyPolicy": {
"staleInProgressMs": 0,
"claimThrashCount": 0,
"reworkLoopCount": 0,
"wipJamMinTasks": 0,
"starvedUrgentMs": 0,
"windowSec": 0
},
"maxRunsPerTask": 0,
"color": "string",
"artifactPolicy": "off",
"defaultReviewer": "string",
"selfApprovalPolicy": "off",
"checkpointPolicy": "off",
"maxTicketTokens": 0,
"tokenPolicy": "off"
}
},
"memberDisplay": {}
}delete_project
Delete a project and ALL nested data (boards, tasks, events, memory). Irreversible. Requires workspace admin or project admin.
Parameters
projectIdstringrequiredargumentReturns
okbooleanrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "delete_project",
"arguments": {
"projectId": "string"
}
}
}const result = await client.callTool("delete_project", {
"projectId": "string"
});result = await session.call_tool("delete_project", arguments={
"projectId": "string"
}){
"ok": true
}add_member
Add or update a member of a project (admin only). How agents join a project.
Parameters
projectIdstringrequiredargumentidentityIdstringrequiredargumentrolestringviewerworkermastermemberadminrequiredargumentReturns
membersobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "add_member",
"arguments": {
"projectId": "string",
"identityId": "string",
"role": "viewer"
}
}
}const result = await client.callTool("add_member", {
"projectId": "string",
"identityId": "string",
"role": "viewer"
});result = await session.call_tool("add_member", arguments={
"projectId": "string",
"identityId": "string",
"role": "viewer"
}){
"members": {}
}remove_member
Remove a member from a project (admin only; cannot remove the last admin).
Parameters
projectIdstringrequiredargumentidentityIdstringrequiredargumentReturns
okbooleanrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "remove_member",
"arguments": {
"projectId": "string",
"identityId": "string"
}
}
}const result = await client.callTool("remove_member", {
"projectId": "string",
"identityId": "string"
});result = await session.call_tool("remove_member", arguments={
"projectId": "string",
"identityId": "string"
}){
"ok": true
}grant_capabilities
Scope ONE member's mutation surface below what its role allows (admin only) — least privilege for autonomous agents. The grant NARROWS within the role, never widens it: the role check still runs, and the call must satisfy both. Fields are all restrictions — tools (allowlist), deny (denylist, wins), labels (task must carry one), boards, maxPriority, statuses (allowed toStatus on move_task), memoryScopes (allowed scope on write_memory). Reads are never restricted, and labels/maxPriority cannot be applied to claim_next (it picks its own task, so it is refused outright) — scope a worker that way and have it run next_task + claim_task. A deny/tools entry naming a WORKSPACE-scoped tool (get_billing, set_billing, set_feature_flags, list_access_requests, decide_access, prune_agent_sessions, disconnect_agent, list_projects, list_agent_sessions) is REFUSED: a grant is stored on the project and those tools take no projectId, so the rule could only ever be stored and never enforced. Reach to them is governed by role alone. Omit scope to apply the least-privilege WORKER default (claim/heartbeat/release/complete/move within READY↔IN_PROGRESS↔REVIEW, add_context_item, task-scope write_memory); pass scope: null to remove the grant so the role governs again. Grants on an ADMIN are not enforced (an admin can rewrite grants anyway).
Body
projectIdstringrequiredidentityIdstringrequiredscopeobject | nullReturns
identityIdstringrequiredgrantobject | nullrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "grant_capabilities",
"arguments": {
"projectId": "string",
"identityId": "string",
"scope": {
"tools": [
"string"
],
"deny": [
"string"
],
"labels": [
"string"
],
"boards": [
"string"
],
"maxPriority": "low",
"statuses": [
"BACKLOG"
],
"memoryScopes": [
"task"
]
}
}
}
}const result = await client.callTool("grant_capabilities", {
"projectId": "string",
"identityId": "string",
"scope": {
"tools": [
"string"
],
"deny": [
"string"
],
"labels": [
"string"
],
"boards": [
"string"
],
"maxPriority": "low",
"statuses": [
"BACKLOG"
],
"memoryScopes": [
"task"
]
}
});result = await session.call_tool("grant_capabilities", arguments={
"projectId": "string",
"identityId": "string",
"scope": {
"tools": [
"string"
],
"deny": [
"string"
],
"labels": [
"string"
],
"boards": [
"string"
],
"maxPriority": "low",
"statuses": [
"BACKLOG"
],
"memoryScopes": [
"task"
]
}
}){
"projectId": "string",
"identityId": "string",
"scope": {
"tools": [
"string"
],
"deny": [
"string"
],
"labels": [
"string"
],
"boards": [
"string"
],
"maxPriority": "low",
"statuses": [
"BACKLOG"
],
"memoryScopes": [
"task"
]
}
}{
"identityId": "string",
"grant": {
"tools": [
"string"
],
"deny": [
"string"
],
"labels": [
"string"
],
"boards": [
"string"
],
"maxPriority": "low",
"statuses": [
"BACKLOG"
],
"memoryScopes": [
"task"
]
}
}update_settings
Update project settings (admin only): review toggle, quota cap (txnCap = successful operations PER HOUR for this project, workspace-admin only, and refused outright when the deployment does not enforce quotas), the status transition table, the score work-selection weights / aging ceiling, and the capabilityAliases synonym map for profile-aware routing (next_task/rank_tasks/claim_next), and the artifactPolicy rung (off|warn|enforce) governing whether complete_task requires produced artifacts, plus defaultReviewer (who a completed ticket is handed to in REVIEW) and selfApprovalPolicy (may the completer approve its own work).
Body
projectIdstringrequiredreviewEnabledbooleanquotaEnabledbooleantxnCapinteger | nullSuccessful operations allowed for this project PER HOUR (fixed hourly window; null = uncapped). Enforcement lags by up to ~70s, so a brief overshoot is expected.
transitionTableobjectmaxTicketTokensinteger[100, 1000000]tokenPolicystringoffwarnenforcecheckpointPolicystringoffwarnenforceselectionWeightsobjectagingCeilingMsinteger<= 9007199254740991capabilityAliasesobjectretryPolicyobjectanomalyPolicyobjectmaxRunsPerTaskinteger<= 50artifactPolicystringoffwarnenforcedefaultReviewerstring | nullselfApprovalPolicystringoffwarnenforcecolorstring | nullReturns
settingsobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "update_settings",
"arguments": {
"projectId": "string",
"reviewEnabled": true,
"quotaEnabled": true,
"txnCap": 0,
"transitionTable": {},
"maxTicketTokens": 100,
"tokenPolicy": "off",
"checkpointPolicy": "off",
"selectionWeights": {
"priority": 0,
"deadline": 0,
"readiness": 0,
"capability": 0,
"aging": 0
},
"agingCeilingMs": 0,
"capabilityAliases": {},
"retryPolicy": {
"baseDelayMs": 0,
"maxDelayMs": 0,
"jitter": 0,
"poisonAfterAttempts": 0
},
"anomalyPolicy": {
"staleInProgressMs": 0,
"claimThrashCount": 0,
"reworkLoopCount": 0,
"wipJamMinTasks": 0,
"starvedUrgentMs": 0,
"windowSec": 0
},
"maxRunsPerTask": 0,
"artifactPolicy": "off",
"defaultReviewer": "string",
"selfApprovalPolicy": "off",
"color": "string"
}
}
}const result = await client.callTool("update_settings", {
"projectId": "string",
"reviewEnabled": true,
"quotaEnabled": true,
"txnCap": 0,
"transitionTable": {},
"maxTicketTokens": 100,
"tokenPolicy": "off",
"checkpointPolicy": "off",
"selectionWeights": {
"priority": 0,
"deadline": 0,
"readiness": 0,
"capability": 0,
"aging": 0
},
"agingCeilingMs": 0,
"capabilityAliases": {},
"retryPolicy": {
"baseDelayMs": 0,
"maxDelayMs": 0,
"jitter": 0,
"poisonAfterAttempts": 0
},
"anomalyPolicy": {
"staleInProgressMs": 0,
"claimThrashCount": 0,
"reworkLoopCount": 0,
"wipJamMinTasks": 0,
"starvedUrgentMs": 0,
"windowSec": 0
},
"maxRunsPerTask": 0,
"artifactPolicy": "off",
"defaultReviewer": "string",
"selfApprovalPolicy": "off",
"color": "string"
});result = await session.call_tool("update_settings", arguments={
"projectId": "string",
"reviewEnabled": true,
"quotaEnabled": true,
"txnCap": 0,
"transitionTable": {},
"maxTicketTokens": 100,
"tokenPolicy": "off",
"checkpointPolicy": "off",
"selectionWeights": {
"priority": 0,
"deadline": 0,
"readiness": 0,
"capability": 0,
"aging": 0
},
"agingCeilingMs": 0,
"capabilityAliases": {},
"retryPolicy": {
"baseDelayMs": 0,
"maxDelayMs": 0,
"jitter": 0,
"poisonAfterAttempts": 0
},
"anomalyPolicy": {
"staleInProgressMs": 0,
"claimThrashCount": 0,
"reworkLoopCount": 0,
"wipJamMinTasks": 0,
"starvedUrgentMs": 0,
"windowSec": 0
},
"maxRunsPerTask": 0,
"artifactPolicy": "off",
"defaultReviewer": "string",
"selfApprovalPolicy": "off",
"color": "string"
}){
"projectId": "string",
"reviewEnabled": true,
"quotaEnabled": true,
"txnCap": 0,
"transitionTable": {},
"maxTicketTokens": 100,
"tokenPolicy": "off",
"checkpointPolicy": "off",
"selectionWeights": {
"priority": 0,
"deadline": 0,
"readiness": 0,
"capability": 0,
"aging": 0
},
"agingCeilingMs": 0,
"capabilityAliases": {},
"retryPolicy": {
"baseDelayMs": 0,
"maxDelayMs": 0,
"jitter": 0,
"poisonAfterAttempts": 0
},
"anomalyPolicy": {
"staleInProgressMs": 0,
"claimThrashCount": 0,
"reworkLoopCount": 0,
"wipJamMinTasks": 0,
"starvedUrgentMs": 0,
"windowSec": 0
},
"maxRunsPerTask": 0,
"artifactPolicy": "off",
"defaultReviewer": "string",
"selfApprovalPolicy": "off",
"color": "string"
}{
"settings": {
"quotaEnabled": true,
"txnCap": 0,
"reviewEnabled": true,
"transitionTable": {},
"selectionWeights": {
"priority": 0,
"deadline": 0,
"readiness": 0,
"capability": 0,
"aging": 0
},
"agingCeilingMs": 0,
"capabilityAliases": {},
"retryPolicy": {
"baseDelayMs": 0,
"maxDelayMs": 0,
"jitter": 0,
"poisonAfterAttempts": 0
},
"anomalyPolicy": {
"staleInProgressMs": 0,
"claimThrashCount": 0,
"reworkLoopCount": 0,
"wipJamMinTasks": 0,
"starvedUrgentMs": 0,
"windowSec": 0
},
"maxRunsPerTask": 0,
"color": "string",
"artifactPolicy": "off",
"defaultReviewer": "string",
"selfApprovalPolicy": "off",
"checkpointPolicy": "off",
"maxTicketTokens": 0,
"tokenPolicy": "off"
}
}create_board
Create a board in a project. Default columns if omitted.
Body
projectIdstringrequirednamestringrequiredcolumnsArray<object>idempotencyKeystringReturns
boardobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "create_board",
"arguments": {
"projectId": "string",
"name": "string",
"columns": [
{
"name": "string",
"status": "BACKLOG",
"wipLimit": 0
}
],
"idempotencyKey": "string"
}
}
}const result = await client.callTool("create_board", {
"projectId": "string",
"name": "string",
"columns": [
{
"name": "string",
"status": "BACKLOG",
"wipLimit": 0
}
],
"idempotencyKey": "string"
});result = await session.call_tool("create_board", arguments={
"projectId": "string",
"name": "string",
"columns": [
{
"name": "string",
"status": "BACKLOG",
"wipLimit": 0
}
],
"idempotencyKey": "string"
}){
"projectId": "string",
"name": "string",
"columns": [
{
"name": "string",
"status": "BACKLOG",
"wipLimit": 0
}
],
"idempotencyKey": "string"
}{
"board": {
"id": "string",
"projectId": "string",
"name": "string",
"columns": [
{
"id": "string",
"name": "string",
"order": 0,
"status": "BACKLOG",
"wipLimit": 0
}
],
"transitions": {}
}
}add_column
Add a column (mapped to a status) to a board.
Body
projectIdstringrequiredboardIdstringrequirednamestringrequiredstatusstringBACKLOGREADYIN_PROGRESSBLOCKEDREVIEWDONECANCELLEDDEAD_LETTERrequiredwipLimitinteger | nullordernumberidempotencyKeystringReturns
columnobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "add_column",
"arguments": {
"projectId": "string",
"boardId": "string",
"name": "string",
"status": "BACKLOG",
"wipLimit": 0,
"order": 0,
"idempotencyKey": "string"
}
}
}const result = await client.callTool("add_column", {
"projectId": "string",
"boardId": "string",
"name": "string",
"status": "BACKLOG",
"wipLimit": 0,
"order": 0,
"idempotencyKey": "string"
});result = await session.call_tool("add_column", arguments={
"projectId": "string",
"boardId": "string",
"name": "string",
"status": "BACKLOG",
"wipLimit": 0,
"order": 0,
"idempotencyKey": "string"
}){
"projectId": "string",
"boardId": "string",
"name": "string",
"status": "BACKLOG",
"wipLimit": 0,
"order": 0,
"idempotencyKey": "string"
}{
"column": {
"id": "string",
"name": "string",
"order": 0,
"status": "BACKLOG",
"wipLimit": 0
}
}list_boards
List boards in a project.
Parameters
projectIdstringrequiredargumentReturns
boardsArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_boards",
"arguments": {
"projectId": "string"
}
}
}const result = await client.callTool("list_boards", {
"projectId": "string"
});result = await session.call_tool("list_boards", arguments={
"projectId": "string"
}){
"boards": [
{
"id": "string",
"projectId": "string",
"name": "string",
"columns": [
{
"id": "string",
"name": "string",
"order": 0,
"status": "BACKLOG",
"wipLimit": 0
}
],
"transitions": {}
}
]
}get_board
Get a board with per-column task counts.
Parameters
projectIdstringrequiredargumentboardIdstringrequiredargumentReturns
boardobjectrequiredtaskCountsobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_board",
"arguments": {
"projectId": "string",
"boardId": "string"
}
}
}const result = await client.callTool("get_board", {
"projectId": "string",
"boardId": "string"
});result = await session.call_tool("get_board", arguments={
"projectId": "string",
"boardId": "string"
}){
"board": {
"id": "string",
"projectId": "string",
"name": "string",
"columns": [
{
"id": "string",
"name": "string",
"order": 0,
"status": "BACKLOG",
"wipLimit": 0
}
],
"transitions": {}
},
"taskCounts": {}
}update_column
Update a board column: rename, change its mapped status, WIP limit, or order.
Body
projectIdstringrequiredboardIdstringrequiredcolumnIdstringrequirednamestringstatusstringBACKLOGREADYIN_PROGRESSBLOCKEDREVIEWDONECANCELLEDDEAD_LETTERwipLimitinteger | nullordernumberReturns
boardobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "update_column",
"arguments": {
"projectId": "string",
"boardId": "string",
"columnId": "string",
"name": "string",
"status": "BACKLOG",
"wipLimit": 0,
"order": 0
}
}
}const result = await client.callTool("update_column", {
"projectId": "string",
"boardId": "string",
"columnId": "string",
"name": "string",
"status": "BACKLOG",
"wipLimit": 0,
"order": 0
});result = await session.call_tool("update_column", arguments={
"projectId": "string",
"boardId": "string",
"columnId": "string",
"name": "string",
"status": "BACKLOG",
"wipLimit": 0,
"order": 0
}){
"projectId": "string",
"boardId": "string",
"columnId": "string",
"name": "string",
"status": "BACKLOG",
"wipLimit": 0,
"order": 0
}{
"board": {
"id": "string",
"projectId": "string",
"name": "string",
"columns": [
{
"id": "string",
"name": "string",
"order": 0,
"status": "BACKLOG",
"wipLimit": 0
}
],
"transitions": {}
}
}remove_column
Remove a board column (must be empty; cannot remove the last column).
Parameters
projectIdstringrequiredargumentboardIdstringrequiredargumentcolumnIdstringrequiredargumentReturns
boardobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "remove_column",
"arguments": {
"projectId": "string",
"boardId": "string",
"columnId": "string"
}
}
}const result = await client.callTool("remove_column", {
"projectId": "string",
"boardId": "string",
"columnId": "string"
});result = await session.call_tool("remove_column", arguments={
"projectId": "string",
"boardId": "string",
"columnId": "string"
}){
"board": {
"id": "string",
"projectId": "string",
"name": "string",
"columns": [
{
"id": "string",
"name": "string",
"order": 0,
"status": "BACKLOG",
"wipLimit": 0
}
],
"transitions": {}
}
}get_transitions
Get a board's status-transition workflow (the table move_task enforces): the effective table, the board's columns, and the column-derived default template.
Parameters
projectIdstringrequiredargumentboardIdstringrequiredargumentReturns
transitionsobjectrequiredcolumnsArray<object>requireddefaultobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_transitions",
"arguments": {
"projectId": "string",
"boardId": "string"
}
}
}const result = await client.callTool("get_transitions", {
"projectId": "string",
"boardId": "string"
});result = await session.call_tool("get_transitions", arguments={
"projectId": "string",
"boardId": "string"
}){
"transitions": {},
"columns": [
{
"id": "string",
"name": "string",
"order": 0,
"status": "BACKLOG",
"wipLimit": 0
}
],
"default": {}
}set_transitions
Replace a board's status-transition workflow. Keys and target values are statuses; a from-status with an empty/omitted list has no outgoing moves. move_task enforces this table; illegal moves -> INVALID_TRANSITION.
Parameters
projectIdstringrequiredargumentboardIdstringrequiredargumenttransitionsobjectrequiredargumentReturns
boardobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "set_transitions",
"arguments": {
"projectId": "string",
"boardId": "string",
"transitions": {}
}
}
}const result = await client.callTool("set_transitions", {
"projectId": "string",
"boardId": "string",
"transitions": {}
});result = await session.call_tool("set_transitions", arguments={
"projectId": "string",
"boardId": "string",
"transitions": {}
}){
"board": {
"id": "string",
"projectId": "string",
"name": "string",
"columns": [
{
"id": "string",
"name": "string",
"order": 0,
"status": "BACKLOG",
"wipLimit": 0
}
],
"transitions": {}
}
}create_task
Create a task on a board (defaults to the first column / its status).
Body
projectIdstringrequiredboardIdstringrequiredtitlestringrequireddescriptionstringprioritystringlownormalhighurgentlabelsArray<string>requiredCapabilitiesArray<string>parentTaskIdstring | nullcolumnIdstringcustomFieldsobjectmodelHintobjectsprintIdstringidempotencyKeystringReturns
taskobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "create_task",
"arguments": {
"projectId": "string",
"boardId": "string",
"title": "string",
"description": "string",
"priority": "low",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"parentTaskId": "string",
"columnId": "string",
"customFields": {},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"sprintId": "string",
"idempotencyKey": "string"
}
}
}const result = await client.callTool("create_task", {
"projectId": "string",
"boardId": "string",
"title": "string",
"description": "string",
"priority": "low",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"parentTaskId": "string",
"columnId": "string",
"customFields": {},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"sprintId": "string",
"idempotencyKey": "string"
});result = await session.call_tool("create_task", arguments={
"projectId": "string",
"boardId": "string",
"title": "string",
"description": "string",
"priority": "low",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"parentTaskId": "string",
"columnId": "string",
"customFields": {},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"sprintId": "string",
"idempotencyKey": "string"
}){
"projectId": "string",
"boardId": "string",
"title": "string",
"description": "string",
"priority": "low",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"parentTaskId": "string",
"columnId": "string",
"customFields": {},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"sprintId": "string",
"idempotencyKey": "string"
}{
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
}get_task
Get a task by id.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentReturns
taskobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_task",
"arguments": {
"projectId": "string",
"taskId": "string"
}
}
}const result = await client.callTool("get_task", {
"projectId": "string",
"taskId": "string"
});result = await session.call_tool("get_task", arguments={
"projectId": "string",
"taskId": "string"
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
}list_tasks
List tasks on a board with optional filters and pagination. missingArtifacts:true returns only tasks carrying no produced artifact — pair it with status:"DONE" to audit finished work for missing evidence. sprintId filters to one sprint's members; it cannot be combined with label (one array-contains per query — VALIDATION).
Body
projectIdstringrequiredboardIdstringrequiredstatusstringBACKLOGREADYIN_PROGRESSBLOCKEDREVIEWDONECANCELLEDDEAD_LETTERassigneestring | nulllabelstringsprintIdstringunblockedOnlybooleanmissingArtifactsbooleanlimitinteger<= 200cursorstringReturns
tasksArray<object>requirednextCursorstring{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_tasks",
"arguments": {
"projectId": "string",
"boardId": "string",
"status": "BACKLOG",
"assignee": "string",
"label": "string",
"sprintId": "string",
"unblockedOnly": true,
"missingArtifacts": true,
"limit": 0,
"cursor": "string"
}
}
}const result = await client.callTool("list_tasks", {
"projectId": "string",
"boardId": "string",
"status": "BACKLOG",
"assignee": "string",
"label": "string",
"sprintId": "string",
"unblockedOnly": true,
"missingArtifacts": true,
"limit": 0,
"cursor": "string"
});result = await session.call_tool("list_tasks", arguments={
"projectId": "string",
"boardId": "string",
"status": "BACKLOG",
"assignee": "string",
"label": "string",
"sprintId": "string",
"unblockedOnly": true,
"missingArtifacts": true,
"limit": 0,
"cursor": "string"
}){
"projectId": "string",
"boardId": "string",
"status": "BACKLOG",
"assignee": "string",
"label": "string",
"sprintId": "string",
"unblockedOnly": true,
"missingArtifacts": true,
"limit": 0,
"cursor": "string"
}{
"tasks": [
{
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
],
"nextCursor": "string"
}update_task
Update task fields (optimistic concurrency via version).
Body
projectIdstringrequiredtaskIdstringrequiredversioninteger[0, 9007199254740991]requiredpatchobjectrequiredReturns
taskobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "update_task",
"arguments": {
"projectId": "string",
"taskId": "string",
"version": 0,
"patch": {
"title": "string",
"description": "string",
"priority": "low",
"labels": [
"string"
],
"assignee": "string",
"estimate": 0,
"dueAt": "string",
"requiredCapabilities": [
"string"
],
"customFields": {},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"reviewer": "string"
}
}
}
}const result = await client.callTool("update_task", {
"projectId": "string",
"taskId": "string",
"version": 0,
"patch": {
"title": "string",
"description": "string",
"priority": "low",
"labels": [
"string"
],
"assignee": "string",
"estimate": 0,
"dueAt": "string",
"requiredCapabilities": [
"string"
],
"customFields": {},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"reviewer": "string"
}
});result = await session.call_tool("update_task", arguments={
"projectId": "string",
"taskId": "string",
"version": 0,
"patch": {
"title": "string",
"description": "string",
"priority": "low",
"labels": [
"string"
],
"assignee": "string",
"estimate": 0,
"dueAt": "string",
"requiredCapabilities": [
"string"
],
"customFields": {},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"reviewer": "string"
}
}){
"projectId": "string",
"taskId": "string",
"version": 0,
"patch": {
"title": "string",
"description": "string",
"priority": "low",
"labels": [
"string"
],
"assignee": "string",
"estimate": 0,
"dueAt": "string",
"requiredCapabilities": [
"string"
],
"customFields": {},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"reviewer": "string"
}
}{
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
}move_task
Move a task to a column/status and/or reorder it (version-checked). REVIEW -> DONE is an APPROVAL: the identity that completed the task should not be the one that approves it, so a self-approval is flagged (default) or rejected with FORBIDDEN per the project's selfApprovalPolicy.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentversioninteger[0, 9007199254740991]requiredargumenttoColumnIdstringargumenttoStatusstringBACKLOGREADYIN_PROGRESSBLOCKEDREVIEWDONECANCELLEDDEAD_LETTERargumentorderstringargumentReturns
taskobjectrequiredwarningsArray<string>{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "move_task",
"arguments": {
"projectId": "string",
"taskId": "string",
"version": 0,
"toColumnId": "string",
"toStatus": "BACKLOG",
"order": "string"
}
}
}const result = await client.callTool("move_task", {
"projectId": "string",
"taskId": "string",
"version": 0,
"toColumnId": "string",
"toStatus": "BACKLOG",
"order": "string"
});result = await session.call_tool("move_task", arguments={
"projectId": "string",
"taskId": "string",
"version": 0,
"toColumnId": "string",
"toStatus": "BACKLOG",
"order": "string"
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"warnings": [
"string"
]
}add_artifact
Bind produced work to this ticket (T-52): the PR/MR/review url, the branch it lives on, the commit shas that landed, file paths, a doc link, or an attachment id — recorded as separate typed entries because they answer different audit questions (what was reviewed vs where the work is). Works with any forge (GitHub, GitLab, Bitbucket, Gerrit, hg) and with no remote at all: an entry needs a url OR a ref. Recording is append-merged and de-duplicated, so it is safe to retry. Prefer doing this BEFORE complete_task — a completion with no artifact is warned or rejected per the project policy.
Body
projectIdstringrequiredtaskIdstringrequiredartifactsArray<object>requiredversioninteger[0, 9007199254740991]Returns
taskobjectrequiredaddedinteger[0, 9007199254740991]required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "add_artifact",
"arguments": {
"projectId": "string",
"taskId": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string"
}
],
"version": 0
}
}
}const result = await client.callTool("add_artifact", {
"projectId": "string",
"taskId": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string"
}
],
"version": 0
});result = await session.call_tool("add_artifact", arguments={
"projectId": "string",
"taskId": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string"
}
],
"version": 0
}){
"projectId": "string",
"taskId": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string"
}
],
"version": 0
}{
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"added": 0
}retract_artifact
Retract a wrongly recorded artifact (T-175): the entry gets a tombstone (who/when/why) instead of being deleted, so the evidence chain keeps its history while the entry stops counting as evidence. This is the correction path add_artifact cannot be: it de-duplicates on (kind, url, ref), which makes a corrected re-record indistinguishable from a retry — silently dropped. Correct in two steps: retract the wrong entry (match by kind + url/ref, plus repo when entries share a key), then add_artifact the right one — a tombstone no longer blocks re-recording its key. Retracts every LIVE entry the selector matches and returns the count. A selector matching NOTHING is NOT_FOUND (a typo must fail loudly, not succeed silently); one matching only tombstones is a safe retry (retracted: 0).
Body
projectIdstringrequiredtaskIdstringrequiredartifactobjectrequiredreasonstringrequiredversioninteger[0, 9007199254740991]Returns
taskobjectrequiredretractedinteger[0, 9007199254740991]required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "retract_artifact",
"arguments": {
"projectId": "string",
"taskId": "string",
"artifact": {
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string"
},
"reason": "string",
"version": 0
}
}
}const result = await client.callTool("retract_artifact", {
"projectId": "string",
"taskId": "string",
"artifact": {
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string"
},
"reason": "string",
"version": 0
});result = await session.call_tool("retract_artifact", arguments={
"projectId": "string",
"taskId": "string",
"artifact": {
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string"
},
"reason": "string",
"version": 0
}){
"projectId": "string",
"taskId": "string",
"artifact": {
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string"
},
"reason": "string",
"version": 0
}{
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"retracted": 0
}add_context_item
Append a context item (note/decision/field/link) to a task. Use kind "link" with a URL body to attach an external artifact (Lucid, Google Docs, Figma, …).
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentkindstringnotedecisionfieldlinkrequiredargumentbodystringrequiredargumentidempotencyKeystringargumentReturns
itemobjectrequiredwarningsArray<string>{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "add_context_item",
"arguments": {
"projectId": "string",
"taskId": "string",
"kind": "note",
"body": "string",
"idempotencyKey": "string"
}
}
}const result = await client.callTool("add_context_item", {
"projectId": "string",
"taskId": "string",
"kind": "note",
"body": "string",
"idempotencyKey": "string"
});result = await session.call_tool("add_context_item", arguments={
"projectId": "string",
"taskId": "string",
"kind": "note",
"body": "string",
"idempotencyKey": "string"
}){
"item": {
"id": "string",
"taskId": "string",
"kind": "note",
"body": "string",
"author": "string",
"ts": "string"
},
"warnings": [
"string"
]
}set_summary
Set a task's rolling context summary (version-checked).
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentversioninteger[0, 9007199254740991]requiredargumentsummarystringrequiredargumentReturns
taskobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "set_summary",
"arguments": {
"projectId": "string",
"taskId": "string",
"version": 0,
"summary": "string"
}
}
}const result = await client.callTool("set_summary", {
"projectId": "string",
"taskId": "string",
"version": 0,
"summary": "string"
});result = await session.call_tool("set_summary", arguments={
"projectId": "string",
"taskId": "string",
"version": 0,
"summary": "string"
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
}create_sprint
Master role. Open a sprint — a named cross-ticket objective — in PROPOSED status on a board. Member tickets are then stamped in via create_task { sprintId } (planted BACKLOG: structurally unclaimable until a human approves the proposal task and the sprint activates) or set_sprint. PROPOSED→ACTIVE is update_sprint, gated on a DONE, bound proposal task.
Parameters
projectIdstringrequiredargumentboardIdstringrequiredargumentnamestringrequiredargumentgoalstringrequiredargumentacceptancestringrequiredargumentidempotencyKeystringargumentReturns
sprintobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "create_sprint",
"arguments": {
"projectId": "string",
"boardId": "string",
"name": "string",
"goal": "string",
"acceptance": "string",
"idempotencyKey": "string"
}
}
}const result = await client.callTool("create_sprint", {
"projectId": "string",
"boardId": "string",
"name": "string",
"goal": "string",
"acceptance": "string",
"idempotencyKey": "string"
});result = await session.call_tool("create_sprint", arguments={
"projectId": "string",
"boardId": "string",
"name": "string",
"goal": "string",
"acceptance": "string",
"idempotencyKey": "string"
}){
"sprint": {
"id": "string",
"projectId": "string",
"boardId": "string",
"name": "string",
"goal": "string",
"acceptance": "string",
"status": "PROPOSED",
"proposalTaskId": "string",
"approvedBy": "string",
"memberCount": 0,
"checkpoint": "string",
"eventCursor": "string",
"createdBy": "string",
"createdAt": "string",
"startedAt": "string",
"closedAt": "string",
"updatedBy": "string",
"updatedAt": "string",
"version": 0
}
}update_sprint
Master role. Version-checked patch over one sprint: metadata (name/goal/acceptance), the proposal-task binding (PROPOSED only; null clears), the supervision checkpoint/eventCursor, and status through the fixed table PROPOSED→ACTIVE|ABANDONED, ACTIVE→CLOSED|ABANDONED. Activation is additionally gated IN the transaction: the caller's project role must be admin, the bound proposal task must be DONE with customFields.sprintProposalFor === sprintId (INVALID_TRANSITION otherwise), and activating your own proposal is refused or flagged per the project's selfApprovalPolicy. Closing with non-terminal members returns a warning, not a refusal.
Body
projectIdstringrequiredsprintIdstringrequiredversioninteger[0, 9007199254740991]requiredpatchobjectrequiredReturns
sprintobjectrequiredwarningsArray<string>{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "update_sprint",
"arguments": {
"projectId": "string",
"sprintId": "string",
"version": 0,
"patch": {
"name": "string",
"goal": "string",
"acceptance": "string",
"status": "PROPOSED",
"proposalTaskId": "string",
"checkpoint": "string",
"eventCursor": "string"
}
}
}
}const result = await client.callTool("update_sprint", {
"projectId": "string",
"sprintId": "string",
"version": 0,
"patch": {
"name": "string",
"goal": "string",
"acceptance": "string",
"status": "PROPOSED",
"proposalTaskId": "string",
"checkpoint": "string",
"eventCursor": "string"
}
});result = await session.call_tool("update_sprint", arguments={
"projectId": "string",
"sprintId": "string",
"version": 0,
"patch": {
"name": "string",
"goal": "string",
"acceptance": "string",
"status": "PROPOSED",
"proposalTaskId": "string",
"checkpoint": "string",
"eventCursor": "string"
}
}){
"projectId": "string",
"sprintId": "string",
"version": 0,
"patch": {
"name": "string",
"goal": "string",
"acceptance": "string",
"status": "PROPOSED",
"proposalTaskId": "string",
"checkpoint": "string",
"eventCursor": "string"
}
}{
"sprint": {
"id": "string",
"projectId": "string",
"boardId": "string",
"name": "string",
"goal": "string",
"acceptance": "string",
"status": "PROPOSED",
"proposalTaskId": "string",
"approvedBy": "string",
"memberCount": 0,
"checkpoint": "string",
"eventCursor": "string",
"createdBy": "string",
"createdAt": "string",
"startedAt": "string",
"closedAt": "string",
"updatedBy": "string",
"updatedAt": "string",
"version": 0
},
"warnings": [
"string"
]
}set_sprint
Master role. Add/remove one task to/from a sprint (version-checked on the TASK). Membership changes are legal only while the sprint is PROPOSED or ACTIVE (INVALID_TRANSITION otherwise); under SPRINT_EXCLUSIVE (the deployment default) a task already in a DIFFERENT sprint refuses with CONFLICT_LOCKED — move = remove then add. The task must live on the sprint’s own board (VALIDATION otherwise). Adding a task already in the target sprint is a retry-safe success no-op.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentversioninteger[0, 9007199254740991]requiredargumentsprintIdstringrequiredargumentactionstringaddremoverequiredargumentReturns
taskobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "set_sprint",
"arguments": {
"projectId": "string",
"taskId": "string",
"version": 0,
"sprintId": "string",
"action": "add"
}
}
}const result = await client.callTool("set_sprint", {
"projectId": "string",
"taskId": "string",
"version": 0,
"sprintId": "string",
"action": "add"
});result = await session.call_tool("set_sprint", arguments={
"projectId": "string",
"taskId": "string",
"version": 0,
"sprintId": "string",
"action": "add"
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
}get_sprint
The cold-resume anchor (spec §6): one composed read returning the sprint doc (goal, acceptance, checkpoint, eventCursor), every member ticket (one indexed query), derived counts/frontier/attention, resume state for the lanes YOU hold, the board workflow and the project policies. A fresh session reads this before touching anything: frontier[0]/attention[0] names the next action, eventCursor closes the watch gap. include adds member deliverables and the durable project-memory window.
Parameters
projectIdstringrequiredargumentsprintIdstringrequiredargumentincludeArray<string>deliverablesmemoryargumentReturns
sprintobjectrequiredtasksArray<object>requiredcountsobjectrequiredfrontierArray<string>requiredattentionArray<object>requiredresumeobjectrequiredworkflowobjectrequiredpoliciesobjectrequiredtruncatedbooleanrequireddeliverablesArray<object>memoryArray<object>{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_sprint",
"arguments": {
"projectId": "string",
"sprintId": "string",
"include": [
"deliverables"
]
}
}
}const result = await client.callTool("get_sprint", {
"projectId": "string",
"sprintId": "string",
"include": [
"deliverables"
]
});result = await session.call_tool("get_sprint", arguments={
"projectId": "string",
"sprintId": "string",
"include": [
"deliverables"
]
}){
"sprint": {
"id": "string",
"projectId": "string",
"boardId": "string",
"name": "string",
"goal": "string",
"acceptance": "string",
"status": "PROPOSED",
"proposalTaskId": "string",
"approvedBy": "string",
"memberCount": 0,
"checkpoint": "string",
"eventCursor": "string",
"createdBy": "string",
"createdAt": "string",
"startedAt": "string",
"closedAt": "string",
"updatedBy": "string",
"updatedAt": "string",
"version": 0
},
"tasks": [
{
"id": "string",
"title": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"claim": {
"agent": "string",
"expiresAt": "string"
},
"hasDeliverable": true
}
],
"counts": {
"total": 0,
"done": 0,
"inProgress": 0,
"review": 0,
"blocked": 0,
"ready": 0,
"backlog": 0,
"deadLetter": 0
},
"frontier": [
"string"
],
"attention": [
{
"taskId": "string",
"why": "review"
}
],
"resume": {},
"workflow": {
"lanes": [
{
"id": "string",
"name": "string",
"order": 0,
"status": "BACKLOG",
"wipLimit": 0
}
],
"transitions": {}
},
"policies": {
"artifactPolicy": "off",
"selfApprovalPolicy": "off",
"defaultReviewer": "string"
},
"truncated": true,
"deliverables": [
{
"taskId": "string",
"title": "string",
"deliverable": "string"
}
],
"memory": [
{
"id": "string",
"taskId": "string",
"scope": "task",
"key": "string",
"gcsUri": "string",
"author": "string",
"ts": "string",
"ttl": "string"
}
]
}list_sprints
A project's sprints, oldest-first, with optional board/status filters and pagination. status:'ACTIVE' is the cold-resume entry point — follow with get_sprint.
Parameters
projectIdstringrequiredargumentboardIdstringargumentstatusstringPROPOSEDACTIVECLOSEDABANDONEDargumentlimitinteger<= 200argumentcursorstringargumentReturns
sprintsArray<object>requirednextCursorstring{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_sprints",
"arguments": {
"projectId": "string",
"boardId": "string",
"status": "PROPOSED",
"limit": 0,
"cursor": "string"
}
}
}const result = await client.callTool("list_sprints", {
"projectId": "string",
"boardId": "string",
"status": "PROPOSED",
"limit": 0,
"cursor": "string"
});result = await session.call_tool("list_sprints", arguments={
"projectId": "string",
"boardId": "string",
"status": "PROPOSED",
"limit": 0,
"cursor": "string"
}){
"sprints": [
{
"id": "string",
"projectId": "string",
"boardId": "string",
"name": "string",
"goal": "string",
"acceptance": "string",
"status": "PROPOSED",
"proposalTaskId": "string",
"approvedBy": "string",
"memberCount": 0,
"checkpoint": "string",
"eventCursor": "string",
"createdBy": "string",
"createdAt": "string",
"startedAt": "string",
"closedAt": "string",
"updatedBy": "string",
"updatedAt": "string",
"version": 0
}
],
"nextCursor": "string"
}claim_task
Claim a READY task (exclusive lease); moves it to IN_PROGRESS. Pass idempotencyKey to make a retry safe: within IDEMPOTENCY_TTL_MS the same key replays the ORIGINAL lease instead of minting a second one (which would invalidate the leaseId your dropped response carried). READ resume IN THE RESPONSE BEFORE YOU TOUCH GIT: non-null means a previous agent already worked this lane — check out resume.branch and continue from resume.lastCheckpoint.NEXT instead of resetting. Only resume: null makes a fresh branch safe.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentleaseSecondsinteger<= 86400argumentidempotencyKeystringargumentReturns
leaseIdstringrequiredtaskobjectrequiredresumeobject | nullrequiredHAS THIS LANE BEEN WORKED BEFORE? null = no prior work exists on this ticket; starting fresh from origin/main is safe. NON-NULL = a previous agent already worked this lane: git fetch origin && git checkout <branch> and continue from lastCheckpoint.NEXT. Do NOT run git reset --hard origin/main — it destroys their commits. Do not re-attempt anything listed under lastCheckpoint.REJECTED.
sprintobject | nullrequiredThe sprint (named objective) the claimed task belongs to, or null. Non-null means this ticket serves a larger goal — read goal before working, and get_sprint for the full frontier.
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "claim_task",
"arguments": {
"projectId": "string",
"taskId": "string",
"leaseSeconds": 0,
"idempotencyKey": "string"
}
}
}const result = await client.callTool("claim_task", {
"projectId": "string",
"taskId": "string",
"leaseSeconds": 0,
"idempotencyKey": "string"
});result = await session.call_tool("claim_task", arguments={
"projectId": "string",
"taskId": "string",
"leaseSeconds": 0,
"idempotencyKey": "string"
}){
"leaseId": "string",
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"resume": {
"branch": "string",
"headCommit": "string",
"lastCheckpoint": {
"DONE": "string",
"NEXT": "string",
"REJECTED": "string",
"UNCERTAIN": "string"
}
},
"sprint": {
"id": "string",
"name": "string",
"goal": "string",
"status": "PROPOSED"
}
}claim_next
Atomically SELECT and CLAIM the best eligible READY task in one server-side op — the scorer and the lock txn fused. On a claim collision it transparently falls through to the next-best candidate WITHIN the same call (bounded to the top 8 by score), so concurrent fleet workers can never pick-then-collide on the same card; returns the first task it actually won (or {task:null} when the frontier is empty/exhausted). The frontier is the top 8 of up to 1000 eligible READY tasks. Optional shard:{index,count} (0 ≤ index < count) stripes the frontier into disjoint lanes; an empty lane falls back to the whole frontier (still bounded to the top 8) so a worker never idles while work exists elsewhere. useProfile:true matches against your registered capability profile (exact · alias · tag) instead of capabilities and orders near-ties by your reliability. maxConcurrency caps how many live claims the caller may already hold (best-effort → RATE_LIMITED, retryable-with-BACKOFF); under useProfile it defaults to your profile's maxConcurrency. Pass idempotencyKey to make a retry safe: within IDEMPOTENCY_TTL_MS the same key returns the SAME task you already won (with its original lease) instead of selecting a second one — without it, a retry after a dropped response leaves you holding two tasks, or reports {task:null} because the one you won is no longer in the READY frontier. Use one claim_next per worker loop instead of next_task + claim_task. READ resume IN THE RESPONSE BEFORE YOU TOUCH GIT: non-null means a previous agent already worked the task you just won — check out resume.branch and continue from resume.lastCheckpoint.NEXT instead of resetting. Only resume: null makes a fresh branch safe.
Body
projectIdstringrequiredboardIdstringrequiredidempotencyKeystringcapabilitiesArray<string>assigneestringsprintIdstringuseProfilebooleanleaseSecondsinteger<= 86400shardobjectmaxConcurrencyinteger<= 9007199254740991Returns
leaseIdstring | nullrequiredtaskobject | nullrequiredattemptsinteger[0, 8]requiredresumeobject | nullrequiredHAS THIS LANE BEEN WORKED BEFORE? null = no prior work exists on this ticket; starting fresh from origin/main is safe. NON-NULL = a previous agent already worked this lane: git fetch origin && git checkout <branch> and continue from lastCheckpoint.NEXT. Do NOT run git reset --hard origin/main — it destroys their commits. Do not re-attempt anything listed under lastCheckpoint.REJECTED.
sprintobject | nullrequiredThe sprint (named objective) the claimed task belongs to, or null. Non-null means this ticket serves a larger goal — read goal before working, and get_sprint for the full frontier.
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "claim_next",
"arguments": {
"projectId": "string",
"boardId": "string",
"idempotencyKey": "string",
"capabilities": [
"string"
],
"assignee": "string",
"sprintId": "string",
"useProfile": true,
"leaseSeconds": 0,
"shard": {
"index": 0,
"count": 0
},
"maxConcurrency": 0
}
}
}const result = await client.callTool("claim_next", {
"projectId": "string",
"boardId": "string",
"idempotencyKey": "string",
"capabilities": [
"string"
],
"assignee": "string",
"sprintId": "string",
"useProfile": true,
"leaseSeconds": 0,
"shard": {
"index": 0,
"count": 0
},
"maxConcurrency": 0
});result = await session.call_tool("claim_next", arguments={
"projectId": "string",
"boardId": "string",
"idempotencyKey": "string",
"capabilities": [
"string"
],
"assignee": "string",
"sprintId": "string",
"useProfile": true,
"leaseSeconds": 0,
"shard": {
"index": 0,
"count": 0
},
"maxConcurrency": 0
}){
"projectId": "string",
"boardId": "string",
"idempotencyKey": "string",
"capabilities": [
"string"
],
"assignee": "string",
"sprintId": "string",
"useProfile": true,
"leaseSeconds": 0,
"shard": {
"index": 0,
"count": 0
},
"maxConcurrency": 0
}{
"leaseId": "string",
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"attempts": 0,
"resume": {
"branch": "string",
"headCommit": "string",
"lastCheckpoint": {
"DONE": "string",
"NEXT": "string",
"REJECTED": "string",
"UNCERTAIN": "string"
}
},
"sprint": {
"id": "string",
"name": "string",
"goal": "string",
"status": "PROPOSED"
}
}heartbeat_task
Extend an active lease without bumping the task version.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentleaseIdstringrequiredargumentleaseSecondsinteger<= 86400argumentReturns
expiresAtstringrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "heartbeat_task",
"arguments": {
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"leaseSeconds": 0
}
}
}const result = await client.callTool("heartbeat_task", {
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"leaseSeconds": 0
});result = await session.call_tool("heartbeat_task", arguments={
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"leaseSeconds": 0
}){
"expiresAt": "string"
}release_task
Release a lease; IN_PROGRESS → READY. Pass idempotencyKey to make a retry safe: within IDEMPOTENCY_TTL_MS the same key replays the original result instead of failing LEASE_EXPIRED on a lease the first call already dropped.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentleaseIdstringrequiredargumentidempotencyKeystringargumentReturns
taskobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "release_task",
"arguments": {
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"idempotencyKey": "string"
}
}
}const result = await client.callTool("release_task", {
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"idempotencyKey": "string"
});result = await session.call_tool("release_task", arguments={
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"idempotencyKey": "string"
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
}complete_task
Complete work: → REVIEW (or DONE when the board skips review). Clears the lease. Pass deliverable to record the work product on the ticket so dependent tasks can build on it (files travel as attachments), and artifacts to BIND the evidence to the ticket — the PR/MR url, the branch, commit shas, file paths (same shape as add_artifact; append-merged and de-duplicated). Completing a ticket with no artifact is warned (default) or rejected per the project's artifactPolicy; label a ticket no-artifact when it genuinely produces none. When the board reviews, the ticket is HANDED OVER: pass reviewer (an agent name or a human identity) — or rely on the ticket's reviewer / the project's defaultReviewer — and the reviewer is recorded on the ticket, made its assignee and notified; the response returns handover: { reviewer, status } so you can sign off naming them. Your lease is released either way: reviewing is not your work, and the identity that completed a task should not be the one that approves it. Pass idempotencyKey to make a retry safe: within IDEMPOTENCY_TTL_MS the same key replays the original result instead of failing LEASE_EXPIRED on a lease this call already dropped.
Body
projectIdstringrequiredtaskIdstringrequiredleaseIdstringrequireddeliverablestringartifactsArray<object>reviewerstringidempotencyKeystringReturns
taskobjectrequiredwarningsArray<string>handoverobject{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "complete_task",
"arguments": {
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string"
}
],
"reviewer": "string",
"idempotencyKey": "string"
}
}
}const result = await client.callTool("complete_task", {
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string"
}
],
"reviewer": "string",
"idempotencyKey": "string"
});result = await session.call_tool("complete_task", arguments={
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string"
}
],
"reviewer": "string",
"idempotencyKey": "string"
}){
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string"
}
],
"reviewer": "string",
"idempotencyKey": "string"
}{
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"warnings": [
"string"
],
"handover": {
"reviewer": "string",
"status": "BACKLOG"
}
}block_task
Mark a task BLOCKED (records blockers); releases the lease. Pass idempotencyKey to make a retry safe: within IDEMPOTENCY_TTL_MS the same key replays the original result (and re-uses, rather than re-creates, the blocker edges) instead of failing LEASE_EXPIRED on a lease this call already dropped.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentleaseIdstringrequiredargumentreasonstringrequiredargumentblockedByArray<string>argumentidempotencyKeystringargumentReturns
taskobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "block_task",
"arguments": {
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"reason": "string",
"blockedBy": [
"string"
],
"idempotencyKey": "string"
}
}
}const result = await client.callTool("block_task", {
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"reason": "string",
"blockedBy": [
"string"
],
"idempotencyKey": "string"
});result = await session.call_tool("block_task", arguments={
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"reason": "string",
"blockedBy": [
"string"
],
"idempotencyKey": "string"
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
}fail_task
Report that a CLAIMED task failed (T-4): drops the lease and either schedules a backed-off retry (→ READY, selectable only after the exponential backoff elapses) or quarantines it (→ DEAD_LETTER) once it is non-retryable or past the project poison threshold. Increments claimAttempts. Pass retryable:false for a permanent failure (straight to quarantine). Use this instead of silently abandoning a lease so the failure is counted and backed off for the whole fleet. Pass idempotencyKey to make a retry safe: within IDEMPOTENCY_TTL_MS the same key replays the original result rather than counting a second attempt against the backoff/poison budget.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentleaseIdstringrequiredargumentreasonstringrequiredargumentretryablebooleanargumentidempotencyKeystringargumentReturns
taskobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "fail_task",
"arguments": {
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"reason": "string",
"retryable": true,
"idempotencyKey": "string"
}
}
}const result = await client.callTool("fail_task", {
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"reason": "string",
"retryable": true,
"idempotencyKey": "string"
});result = await session.call_tool("fail_task", arguments={
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"reason": "string",
"retryable": true,
"idempotencyKey": "string"
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
}requeue_task
Requeue a quarantined (DEAD_LETTER) task (T-4): → READY with claimAttempts and the retry backoff reset to 0, so the fleet can retry it once a human has addressed the root cause. MASTER role — quarantine exists to force escalation, so the worker that poisoned a ticket cannot un-poison it and retry itself; only valid on a DEAD_LETTER task (else INVALID_TRANSITION).
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentReturns
taskobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "requeue_task",
"arguments": {
"projectId": "string",
"taskId": "string"
}
}
}const result = await client.callTool("requeue_task", {
"projectId": "string",
"taskId": "string"
});result = await session.call_tool("requeue_task", arguments={
"projectId": "string",
"taskId": "string"
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
}reap_stale_leases
On-demand dead-lease sweep for a board (T-4): flip every IN_PROGRESS task whose lease has expired back to READY (with retry backoff) or DEAD_LETTER (if poisoned), counting the failed attempt. This also happens automatically when next_task/wait_for_task/claim_next find no claimable work (lazy recovery, no background sweeper) — call this to force a full sweep regardless (e.g. before rendering a human board view). Returns the count reaped.
Parameters
projectIdstringrequiredargumentboardIdstringrequiredargumentReturns
reapedinteger[0, 9007199254740991]required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "reap_stale_leases",
"arguments": {
"projectId": "string",
"boardId": "string"
}
}
}const result = await client.callTool("reap_stale_leases", {
"projectId": "string",
"boardId": "string"
});result = await session.call_tool("reap_stale_leases", arguments={
"projectId": "string",
"boardId": "string"
}){
"reaped": 0
}handoff_task
Hand a task to another agent: release lease, reassign, leave a note.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentleaseIdstringrequiredargumenttoAgentstringrequiredargumentmemoryNotestringrequiredargumentReturns
taskobjectrequiredwarningsArray<string>{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "handoff_task",
"arguments": {
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"toAgent": "string",
"memoryNote": "string"
}
}
}const result = await client.callTool("handoff_task", {
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"toAgent": "string",
"memoryNote": "string"
});result = await session.call_tool("handoff_task", arguments={
"projectId": "string",
"taskId": "string",
"leaseId": "string",
"toAgent": "string",
"memoryNote": "string"
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"warnings": [
"string"
]
}next_task
Highest-priority unblocked, unclaimed, eligible READY task on a board (or null). Pass assignee to pull only the tasks routed to that agent name. strategy:"score" (or explain:true) ranks the eligible pool with a cost-aware scorer (priority · deadline urgency · bottleneck fan-out · capability fit · bounded aging) instead of FIFO-priority — zero extra reads; omit it for the exact legacy order. useProfile:true matches against your registered capability profile (exact · alias · tag, register_agent_profile) instead of the capabilities arg and breaks near-ties by your proven reliability. explain:true adds the per-factor breakdown + the runner-up (+ tier/reliability under useProfile). includeInbox:true (T-149) also sweeps REVIEW/BLOCKED/DEAD_LETTER for assignee in the SAME call — the #1 way a worker looks stuck is a ticket sitting in REVIEW, invisible to the READY-only selection above; each bucket is bounded by inboxLimit (default 20) and truncated says so when a bucket is capped.
Parameters
projectIdstringrequiredargumentboardIdstringrequiredargumentcapabilitiesArray<string>argumentassigneestringargumentsprintIdstringargumentstrategystringfifoscoreargumentexplainbooleanargumentuseProfilebooleanargumentincludeInboxbooleanargumentinboxLimitinteger<= 100argumentReturns
taskobject | nullrequiredexplanationobjectinboxobject{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "next_task",
"arguments": {
"projectId": "string",
"boardId": "string",
"capabilities": [
"string"
],
"assignee": "string",
"sprintId": "string",
"strategy": "fifo",
"explain": true,
"useProfile": true,
"includeInbox": true,
"inboxLimit": 0
}
}
}const result = await client.callTool("next_task", {
"projectId": "string",
"boardId": "string",
"capabilities": [
"string"
],
"assignee": "string",
"sprintId": "string",
"strategy": "fifo",
"explain": true,
"useProfile": true,
"includeInbox": true,
"inboxLimit": 0
});result = await session.call_tool("next_task", arguments={
"projectId": "string",
"boardId": "string",
"capabilities": [
"string"
],
"assignee": "string",
"sprintId": "string",
"strategy": "fifo",
"explain": true,
"useProfile": true,
"includeInbox": true,
"inboxLimit": 0
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"explanation": {
"score": 0,
"reason": "string",
"agedOut": true,
"factors": {
"priority": {
"value": 0,
"weight": 0,
"contribution": 0
},
"deadline": {
"value": 0,
"weight": 0,
"contribution": 0
},
"readiness": {
"value": 0,
"weight": 0,
"contribution": 0
},
"capability": {
"value": 0,
"weight": 0,
"contribution": 0
},
"aging": {
"value": 0,
"weight": 0,
"contribution": 0
}
},
"runnerUp": {
"taskId": "string",
"score": 0,
"reason": "string"
},
"tier": "exact",
"reliability": 0
},
"inbox": {
"review": [
{
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": "[circular]",
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
],
"blocked": [
{
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
],
"deadLetter": [
{
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
],
"truncated": true
}
}rank_tasks
Explainable ranking of the claimable READY pool by the cost-aware scorer (priority · deadline urgency · bottleneck fan-out · capability fit · bounded aging) — returns { ranked: {task, score, reason, agedOut, factors, tier?, reliability?}[] }, highest first. A read-only sibling of next_task (zero extra Firestore reads): use it to see WHY work is ordered and what the runners-up are. Pass assignee/capabilities to scope, useProfile:true to rank against your registered capability profile (adds the match tier + reliability), limit to cap.
Parameters
projectIdstringrequiredargumentboardIdstringrequiredargumentcapabilitiesArray<string>argumentassigneestringargumentsprintIdstringargumentuseProfilebooleanargumentlimitinteger<= 200argumentReturns
rankedArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "rank_tasks",
"arguments": {
"projectId": "string",
"boardId": "string",
"capabilities": [
"string"
],
"assignee": "string",
"sprintId": "string",
"useProfile": true,
"limit": 0
}
}
}const result = await client.callTool("rank_tasks", {
"projectId": "string",
"boardId": "string",
"capabilities": [
"string"
],
"assignee": "string",
"sprintId": "string",
"useProfile": true,
"limit": 0
});result = await session.call_tool("rank_tasks", arguments={
"projectId": "string",
"boardId": "string",
"capabilities": [
"string"
],
"assignee": "string",
"sprintId": "string",
"useProfile": true,
"limit": 0
}){
"ranked": [
{
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"score": 0,
"reason": "string",
"agedOut": true,
"factors": {
"priority": {
"value": 0,
"weight": 0,
"contribution": 0
},
"deadline": {
"value": 0,
"weight": 0,
"contribution": 0
},
"readiness": {
"value": 0,
"weight": 0,
"contribution": 0
},
"capability": {
"value": 0,
"weight": 0,
"contribution": 0
},
"aging": {
"value": 0,
"weight": 0,
"contribution": 0
}
},
"tier": "exact",
"reliability": 0
}
]
}wait_for_task
Long-poll next_task: blocks until a claimable READY task appears on the board, or timeoutSec (default 25, max 50) elapses — then returns {task: null}. Re-call in a loop instead of polling next_task. Pass assignee to wake only on tasks routed to that agent name (the board-assignment inbox); pass capabilities to filter by skill. resume describes the SELECTED task (this tool selects, it does not claim): non-null means a previous agent already worked it — check out resume.branch and continue from resume.lastCheckpoint.NEXT rather than resetting. Only resume: null makes a fresh branch safe.
Parameters
projectIdstringrequiredargumentboardIdstringrequiredargumentcapabilitiesArray<string>argumentassigneestringargumentsprintIdstringargumenttimeoutSecinteger<= 50argumentReturns
taskobject | nullrequiredresumeobject | nullrequiredHAS THIS LANE BEEN WORKED BEFORE? null = no prior work exists on this ticket; starting fresh from origin/main is safe. NON-NULL = a previous agent already worked this lane: git fetch origin && git checkout <branch> and continue from lastCheckpoint.NEXT. Do NOT run git reset --hard origin/main — it destroys their commits. Do not re-attempt anything listed under lastCheckpoint.REJECTED.
sprintobject | nullrequiredThe sprint (named objective) the claimed task belongs to, or null. Non-null means this ticket serves a larger goal — read goal before working, and get_sprint for the full frontier.
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "wait_for_task",
"arguments": {
"projectId": "string",
"boardId": "string",
"capabilities": [
"string"
],
"assignee": "string",
"sprintId": "string",
"timeoutSec": 0
}
}
}const result = await client.callTool("wait_for_task", {
"projectId": "string",
"boardId": "string",
"capabilities": [
"string"
],
"assignee": "string",
"sprintId": "string",
"timeoutSec": 0
});result = await session.call_tool("wait_for_task", arguments={
"projectId": "string",
"boardId": "string",
"capabilities": [
"string"
],
"assignee": "string",
"sprintId": "string",
"timeoutSec": 0
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"resume": {
"branch": "string",
"headCommit": "string",
"lastCheckpoint": {
"DONE": "string",
"NEXT": "string",
"REJECTED": "string",
"UNCERTAIN": "string"
}
},
"sprint": {
"id": "string",
"name": "string",
"goal": "string",
"status": "PROPOSED"
}
}orchestrate_subtasks
LEADER fan-out (map-reduce / leader-worker): decompose the task you hold into child tasks and park yourself, in ONE transaction. Creates each child READY (claimable immediately) under parentTaskId on the same board, wires a blocks-edge from every child to the parent, then moves the parent to BLOCKED and DROPS your lease — so you stop burning a worker slot and a heartbeat while the children run. Requires the master persona AND a live lease on the parent (else LEASE_EXPIRED). Follow it with wait_for_children to await the join at ~0 reads; when the last child reaches DONE, auto-unblock returns the parent to READY and you must RE-CLAIM it to finish the reduce step. Rejects a fan-out that would close a dependency cycle (CYCLE_DETECTED); nothing is written when it does.
Body
projectIdstringrequiredparentTaskIdstringrequiredleaseIdstringrequiredchildrenArray<object>requiredidempotencyKeystringReturns
parentobjectrequiredchildrenArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "orchestrate_subtasks",
"arguments": {
"projectId": "string",
"parentTaskId": "string",
"leaseId": "string",
"children": [
{
"title": "string",
"description": "string",
"requiredCapabilities": [
"string"
],
"priority": "low"
}
],
"idempotencyKey": "string"
}
}
}const result = await client.callTool("orchestrate_subtasks", {
"projectId": "string",
"parentTaskId": "string",
"leaseId": "string",
"children": [
{
"title": "string",
"description": "string",
"requiredCapabilities": [
"string"
],
"priority": "low"
}
],
"idempotencyKey": "string"
});result = await session.call_tool("orchestrate_subtasks", arguments={
"projectId": "string",
"parentTaskId": "string",
"leaseId": "string",
"children": [
{
"title": "string",
"description": "string",
"requiredCapabilities": [
"string"
],
"priority": "low"
}
],
"idempotencyKey": "string"
}){
"projectId": "string",
"parentTaskId": "string",
"leaseId": "string",
"children": [
{
"title": "string",
"description": "string",
"requiredCapabilities": [
"string"
],
"priority": "low"
}
],
"idempotencyKey": "string"
}{
"parent": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"children": [
{
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
]
}wait_for_children
Long-poll the JOIN of a leader/worker fan-out: blocks until every child that BLOCKS taskId has reached DONE (its blockers cleared), or timeoutSec (default 25, max 50) elapses. Only children wired with a blocks-edge count — a plain add_subtask child gates nothing and is excluded, so pair this with orchestrate_subtasks (or add the edges yourself). Returns { remaining, doneChildIds } — re-call in a loop while remaining > 0 instead of polling list_subtasks. Costs ~0 reads while idle (parks on the board's shared listener and only re-probes when the board actually changes). When it returns remaining:0 the parent has been auto-unblocked to READY — RE-CLAIM it (claim_task) to finish the reduce step. NOTE: only DONE clears a blocker, so a child parked in REVIEW, CANCELLED or DEAD_LETTER keeps remaining above zero indefinitely; treat a run of empty-progress timeouts as a signal to inspect the children, not to keep waiting.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumenttimeoutSecinteger<= 50argumentReturns
remaininginteger[0, 9007199254740991]requireddoneChildIdsArray<string>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "wait_for_children",
"arguments": {
"projectId": "string",
"taskId": "string",
"timeoutSec": 0
}
}
}const result = await client.callTool("wait_for_children", {
"projectId": "string",
"taskId": "string",
"timeoutSec": 0
});result = await session.call_tool("wait_for_children", arguments={
"projectId": "string",
"taskId": "string",
"timeoutSec": 0
}){
"remaining": 0,
"doneChildIds": [
"string"
]
}wait_for_updates
Long-poll the board's event feed — the supervisor-side companion to wait_for_task, and what the gateway drives the human live-UI push from. Blocks until events after sinceCursor land on the board (task transitions, completions, follow-ups, …), or timeoutSec (default 25, max 50) elapses — then returns { events: [] } with the cursor unchanged. First call WITHOUT sinceCursor returns immediately with the current cursor; save cursor and re-call with it in a loop (events come oldest-first; a large burst drains over successive calls). The cursor is an opaque ts|id token — same-millisecond siblings are ordered deterministically so none are dropped. Costs ~0 reads while idle (parks on the board listener) — use this instead of polling list_tasks to watch a board.
Parameters
projectIdstringrequiredargumentboardIdstringrequiredargumentsinceCursorstringargumenttimeoutSecinteger<= 50argumentlimitinteger<= 200argumentReturns
eventsArray<object>requiredcursorstringrequiredtruncatedbooleanrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "wait_for_updates",
"arguments": {
"projectId": "string",
"boardId": "string",
"sinceCursor": "string",
"timeoutSec": 0,
"limit": 0
}
}
}const result = await client.callTool("wait_for_updates", {
"projectId": "string",
"boardId": "string",
"sinceCursor": "string",
"timeoutSec": 0,
"limit": 0
});result = await session.call_tool("wait_for_updates", arguments={
"projectId": "string",
"boardId": "string",
"sinceCursor": "string",
"timeoutSec": 0,
"limit": 0
}){
"events": [
{
"id": "string",
"projectId": "string",
"boardId": "string",
"taskId": "string",
"actor": "string",
"agent": "string",
"type": "string",
"ts": "string"
}
],
"cursor": "string",
"truncated": true
}write_memory
Write a memory entry. Task scopes (task/agent/shared) need a taskId and die with the task; the durable scopes outlive it — project (a fact every member should reuse) and agent_global (private to you, follows you across projects). Read durable entries back with recall_memory. Optional TTL.
Body
projectIdstringrequiredtaskIdstringscopestringtaskagentsharedprojectagent_globalrequiredkeystringrequiredvalueanylargeArtifactobjectttlstringidempotencyKeystringReturns
entryIdstringrequireduploadUrlstringwarningsArray<string>{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "write_memory",
"arguments": {
"projectId": "string",
"taskId": "string",
"scope": "task",
"key": "string",
"largeArtifact": {
"fileName": "string",
"mimeType": "string"
},
"ttl": "string",
"idempotencyKey": "string"
}
}
}const result = await client.callTool("write_memory", {
"projectId": "string",
"taskId": "string",
"scope": "task",
"key": "string",
"largeArtifact": {
"fileName": "string",
"mimeType": "string"
},
"ttl": "string",
"idempotencyKey": "string"
});result = await session.call_tool("write_memory", arguments={
"projectId": "string",
"taskId": "string",
"scope": "task",
"key": "string",
"largeArtifact": {
"fileName": "string",
"mimeType": "string"
},
"ttl": "string",
"idempotencyKey": "string"
}){
"projectId": "string",
"taskId": "string",
"scope": "task",
"key": "string",
"largeArtifact": {
"fileName": "string",
"mimeType": "string"
},
"ttl": "string",
"idempotencyKey": "string"
}{
"entryId": "string",
"uploadUrl": "string",
"warnings": [
"string"
]
}read_memory
Read the memory entries attached to a task (agent scope is private to the caller). For durable project/agent_global memory use recall_memory.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentscopestringtaskagentsharedargumentkeystringargumentReturns
entriesArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "read_memory",
"arguments": {
"projectId": "string",
"taskId": "string",
"scope": "task",
"key": "string"
}
}
}const result = await client.callTool("read_memory", {
"projectId": "string",
"taskId": "string",
"scope": "task",
"key": "string"
});result = await session.call_tool("read_memory", arguments={
"projectId": "string",
"taskId": "string",
"scope": "task",
"key": "string"
}){
"entries": [
{
"id": "string",
"taskId": "string",
"scope": "task",
"key": "string",
"gcsUri": "string",
"author": "string",
"ts": "string",
"ttl": "string"
}
]
}recall_memory
Recall durable memory that outlived the task it was learned on: project (written by any member, visible to all of them) and agent_global (private to you, across every project you work). Give key for an exact lookup, otherwise the most recent k entries. Omit scope with a projectId to get both, newest first.
Parameters
projectIdstringargumentscopestringprojectagent_globalargumentkeystringargumentkinteger<= 100argumentReturns
entriesArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "recall_memory",
"arguments": {
"projectId": "string",
"scope": "project",
"key": "string",
"k": 0
}
}
}const result = await client.callTool("recall_memory", {
"projectId": "string",
"scope": "project",
"key": "string",
"k": 0
});result = await session.call_tool("recall_memory", arguments={
"projectId": "string",
"scope": "project",
"key": "string",
"k": 0
}){
"entries": [
{
"id": "string",
"taskId": "string",
"scope": "task",
"key": "string",
"gcsUri": "string",
"author": "string",
"ts": "string",
"ttl": "string"
}
]
}get_task_context
Composed, token-budgeted briefing for a task (summary, fields, items, deps, attachments, memory, deliverable, artifacts). Set includeUpstream to also pull the deliverables of the tasks this one depends on, so a tool can build on what came before. CALL THIS BEFORE YOU CREATE A BRANCH and read resume: non-null means a previous agent already worked this lane — check out resume.branch and continue from resume.lastCheckpoint.NEXT, and do NOT git reset --hard origin/main. Only resume: null makes a fresh branch safe. resume is always returned, whatever you pass in include, limit or cursor. Set includeProjectMemory to fold in exactly what recall_memory { projectId } would return for you (durable project facts + your own agent_global memory) — one claim-time read instead of a separate call.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentincludeArray<string>summaryfieldsitemsdepsattachmentsmemoryfollowupsargumentincludeUpstreambooleanargumentincludeProjectMemorybooleanargumentlimitinteger<= 100argumentcursorstringargumentReturns
summarystringrequiredfieldsobjectrequireditemsArray<object>requireddepsobjectrequiredattachmentsArray<object>requiredmemoryArray<object>requiredopenFollowUpsArray<object>requiredopenFollowUpCountnumberrequireddeliverablestring | nullartifactsArray<object>requiredreviewobject | nullrequiredresumeobject | nullrequiredHAS THIS LANE BEEN WORKED BEFORE? null = no prior work exists on this ticket; starting fresh from origin/main is safe. NON-NULL = a previous agent already worked this lane: git fetch origin && git checkout <branch> and continue from lastCheckpoint.NEXT. Do NOT run git reset --hard origin/main — it destroys their commits. Do not re-attempt anything listed under lastCheckpoint.REJECTED.
modelHintobject | nullrequiredworkflowobjectrequiredupstreamArray<object>projectMemoryArray<object>nextCursorstring{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_task_context",
"arguments": {
"projectId": "string",
"taskId": "string",
"include": [
"summary"
],
"includeUpstream": true,
"includeProjectMemory": true,
"limit": 0,
"cursor": "string"
}
}
}const result = await client.callTool("get_task_context", {
"projectId": "string",
"taskId": "string",
"include": [
"summary"
],
"includeUpstream": true,
"includeProjectMemory": true,
"limit": 0,
"cursor": "string"
});result = await session.call_tool("get_task_context", arguments={
"projectId": "string",
"taskId": "string",
"include": [
"summary"
],
"includeUpstream": true,
"includeProjectMemory": true,
"limit": 0,
"cursor": "string"
}){
"summary": "string",
"fields": {},
"items": [
{
"id": "string",
"taskId": "string",
"kind": "note",
"body": "string",
"author": "string",
"ts": "string"
}
],
"deps": {
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"edges": [
{
"id": "string",
"projectId": "string",
"fromTaskId": "string",
"toTaskId": "string",
"type": "blocks",
"createdBy": "string",
"ts": "string"
}
]
},
"attachments": [
{
"id": "string",
"taskId": "string",
"gcsUri": "string",
"mimeType": "string",
"bytes": 0,
"sha256": "string",
"thumbUri": "string",
"extractedText": "string",
"kind": "file",
"status": "pending",
"createdBy": "string",
"ts": "string"
}
],
"memory": [
{
"id": "string",
"taskId": "string",
"scope": "task",
"key": "string",
"gcsUri": "string",
"author": "string",
"ts": "string",
"ttl": "string"
}
],
"openFollowUps": [
{
"id": "string",
"taskId": "string",
"projectId": "string",
"body": "string",
"author": "string",
"target": "string",
"status": "open",
"reopened": true,
"ts": "string"
}
],
"openFollowUpCount": 0,
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"resume": {
"branch": "string",
"headCommit": "string",
"lastCheckpoint": {
"DONE": "string",
"NEXT": "string",
"REJECTED": "string",
"UNCERTAIN": "string"
}
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"workflow": {
"status": "BACKLOG",
"allowedMoves": [
"BACKLOG"
],
"lanes": [
{
"id": "string",
"name": "string",
"order": 0,
"status": "BACKLOG",
"wipLimit": 0
}
]
},
"upstream": [
{
"taskId": "string",
"title": "string",
"status": "string",
"deliverable": "string",
"summary": "string"
}
],
"projectMemory": [
{
"id": "string",
"taskId": "string",
"scope": "task",
"key": "string",
"gcsUri": "string",
"author": "string",
"ts": "string",
"ttl": "string"
}
],
"nextCursor": "string"
}add_dependency
Add a dependency edge (blocks/relates). Rejects cycles in the blocks graph.
Parameters
projectIdstringrequiredargumentfromTaskIdstringrequiredargumenttoTaskIdstringrequiredargumenttypestringblocksrelatesargumentidempotencyKeystringargumentReturns
edgeobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "add_dependency",
"arguments": {
"projectId": "string",
"fromTaskId": "string",
"toTaskId": "string",
"type": "blocks",
"idempotencyKey": "string"
}
}
}const result = await client.callTool("add_dependency", {
"projectId": "string",
"fromTaskId": "string",
"toTaskId": "string",
"type": "blocks",
"idempotencyKey": "string"
});result = await session.call_tool("add_dependency", arguments={
"projectId": "string",
"fromTaskId": "string",
"toTaskId": "string",
"type": "blocks",
"idempotencyKey": "string"
}){
"edge": {
"id": "string",
"projectId": "string",
"fromTaskId": "string",
"toTaskId": "string",
"type": "blocks",
"createdBy": "string",
"ts": "string"
}
}remove_dependency
Remove a dependency edge.
Parameters
projectIdstringrequiredargumentedgeIdstringrequiredargumentReturns
okbooleanrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "remove_dependency",
"arguments": {
"projectId": "string",
"edgeId": "string"
}
}
}const result = await client.callTool("remove_dependency", {
"projectId": "string",
"edgeId": "string"
});result = await session.call_tool("remove_dependency", arguments={
"projectId": "string",
"edgeId": "string"
}){
"ok": true
}add_subtask
Create a child task under a parent (same board).
Parameters
projectIdstringrequiredargumentparentTaskIdstringrequiredargumenttitlestringrequiredargumentdescriptionstringargumentprioritystringlownormalhighurgentargumentidempotencyKeystringargumentReturns
taskobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "add_subtask",
"arguments": {
"projectId": "string",
"parentTaskId": "string",
"title": "string",
"description": "string",
"priority": "low",
"idempotencyKey": "string"
}
}
}const result = await client.callTool("add_subtask", {
"projectId": "string",
"parentTaskId": "string",
"title": "string",
"description": "string",
"priority": "low",
"idempotencyKey": "string"
});result = await session.call_tool("add_subtask", arguments={
"projectId": "string",
"parentTaskId": "string",
"title": "string",
"description": "string",
"priority": "low",
"idempotencyKey": "string"
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
}list_subtasks
List child tasks of a parent.
Parameters
projectIdstringrequiredargumentparentTaskIdstringrequiredargumentReturns
tasksArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_subtasks",
"arguments": {
"projectId": "string",
"parentTaskId": "string"
}
}
}const result = await client.callTool("list_subtasks", {
"projectId": "string",
"parentTaskId": "string"
});result = await session.call_tool("list_subtasks", arguments={
"projectId": "string",
"parentTaskId": "string"
}){
"tasks": [
{
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
]
}add_comment
Add a threaded comment to a task (T-32). body is markdown; pass parentCommentId to reply to another comment (omit for a top-level comment). @mentions in the body that match a project member are resolved and recorded so the mentioned member gets a notification — a token matches the member id, their account email or display name, or the local-part of either (@alice matches alice@x.com). Rides the event log (comment.added) and the live feed.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentbodystringrequiredargumentparentCommentIdstringargumentidempotencyKeystringargumentReturns
commentobjectrequiredwarningsArray<string>{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "add_comment",
"arguments": {
"projectId": "string",
"taskId": "string",
"body": "string",
"parentCommentId": "string",
"idempotencyKey": "string"
}
}
}const result = await client.callTool("add_comment", {
"projectId": "string",
"taskId": "string",
"body": "string",
"parentCommentId": "string",
"idempotencyKey": "string"
});result = await session.call_tool("add_comment", arguments={
"projectId": "string",
"taskId": "string",
"body": "string",
"parentCommentId": "string",
"idempotencyKey": "string"
}){
"comment": {
"id": "string",
"taskId": "string",
"author": "string",
"body": "string",
"parentCommentId": "string",
"mentions": [
"string"
],
"ts": "string"
},
"warnings": [
"string"
]
}list_comments
List a task’s comments oldest-first (chronological thread order). Paginated via cursor.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentlimitinteger<= 200argumentcursorstringargumentReturns
commentsArray<object>requirednextCursorstring{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_comments",
"arguments": {
"projectId": "string",
"taskId": "string",
"limit": 0,
"cursor": "string"
}
}
}const result = await client.callTool("list_comments", {
"projectId": "string",
"taskId": "string",
"limit": 0,
"cursor": "string"
});result = await session.call_tool("list_comments", arguments={
"projectId": "string",
"taskId": "string",
"limit": 0,
"cursor": "string"
}){
"comments": [
{
"id": "string",
"taskId": "string",
"author": "string",
"body": "string",
"parentCommentId": "string",
"mentions": [
"string"
],
"ts": "string"
}
],
"nextCursor": "string"
}create_chronicle_page
Start a Chronicle record by hand — a document nobody generated. Every block is authored: provenance is computed server-side and origin is IGNORED here exactly as it is on an edit, because only a sweep may assert that text was derived from something. The slug is the record's identity and cannot be changed later; a slug already in use is refused rather than merged into, so nothing you write is silently discarded. Block ids shaped like the sweep’s generated ones ((cov|ctx|dec|rej|chk|ev)N/M) are reserved and refused, so a later ingest of the same slug can never fold a hand-written block with a derived one. Use edit_chronicle_page to change a record afterwards — blocks may be added, removed and reordered there.
Body
projectIdstringrequiredkindstringadrtopicfeaturediagramcontractrequiredslugstringrequiredtitlestringrequiredblocksArray<object>requiredidempotencyKeystringReturns
pageIdstringrequiredversionIdstringrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "create_chronicle_page",
"arguments": {
"projectId": "string",
"kind": "adr",
"slug": "string",
"title": "string",
"blocks": [
{
"id": "string",
"text": "string",
"citations": [],
"origin": null
}
],
"idempotencyKey": "string"
}
}
}const result = await client.callTool("create_chronicle_page", {
"projectId": "string",
"kind": "adr",
"slug": "string",
"title": "string",
"blocks": [
{
"id": "string",
"text": "string",
"citations": [],
"origin": null
}
],
"idempotencyKey": "string"
});result = await session.call_tool("create_chronicle_page", arguments={
"projectId": "string",
"kind": "adr",
"slug": "string",
"title": "string",
"blocks": [
{
"id": "string",
"text": "string",
"citations": [],
"origin": null
}
],
"idempotencyKey": "string"
}){
"projectId": "string",
"kind": "adr",
"slug": "string",
"title": "string",
"blocks": [
{
"id": "string",
"text": "string",
"citations": [],
"origin": null
}
],
"idempotencyKey": "string"
}{
"pageId": "string",
"versionId": "string"
}ingest_chronicle_page
idempotentIngest a generated Chronicle record into the hosted surface (repo -> hosted, one way). Creates the page on first sight, and on re-ingest MERGES the new derivation with any human edits rather than overwriting them: unchanged blocks take the new text, human-edited blocks are kept, blocks changed on both sides are returned as conflicts for a human to land. Idempotent — re-ingesting an unchanged record writes no new version. Block provenance is computed server-side and cannot be supplied. topics, coverage and supersededBy carry the generated record's own frontmatter onto the page so the index can count, filter and search by them; each is optional, and OMITTING one leaves the stored value alone while an empty array (or a null coverage) clears it.
Body
projectIdstringrequiredkindstringadrtopicfeaturediagramcontractrequiredslugstringrequiredtitlestringrequiredsourcePathstring | nullnulltopicsArray<string>coveragestring | nullsupersededByArray<string>blocksArray<object>requiredidempotencyKeystringReturns
pageIdstringrequiredversionIdstringrequiredcreatedbooleanrequiredunchangedbooleanrequiredconflictsArray<string>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "ingest_chronicle_page",
"arguments": {
"projectId": "string",
"kind": "adr",
"slug": "string",
"title": "string",
"sourcePath": null,
"topics": [
"string"
],
"coverage": "full",
"supersededBy": [
"string"
],
"blocks": [
{
"id": "string",
"text": "string",
"citations": [],
"origin": null
}
],
"idempotencyKey": "string"
}
}
}const result = await client.callTool("ingest_chronicle_page", {
"projectId": "string",
"kind": "adr",
"slug": "string",
"title": "string",
"sourcePath": null,
"topics": [
"string"
],
"coverage": "full",
"supersededBy": [
"string"
],
"blocks": [
{
"id": "string",
"text": "string",
"citations": [],
"origin": null
}
],
"idempotencyKey": "string"
});result = await session.call_tool("ingest_chronicle_page", arguments={
"projectId": "string",
"kind": "adr",
"slug": "string",
"title": "string",
"sourcePath": null,
"topics": [
"string"
],
"coverage": "full",
"supersededBy": [
"string"
],
"blocks": [
{
"id": "string",
"text": "string",
"citations": [],
"origin": null
}
],
"idempotencyKey": "string"
}){
"projectId": "string",
"kind": "adr",
"slug": "string",
"title": "string",
"sourcePath": null,
"topics": [
"string"
],
"coverage": "full",
"supersededBy": [
"string"
],
"blocks": [
{
"id": "string",
"text": "string",
"citations": [],
"origin": null
}
],
"idempotencyKey": "string"
}{
"pageId": "string",
"versionId": "string",
"created": true,
"unchanged": true,
"conflicts": [
"string"
]
}edit_chronicle_page
Commit a new version of a Chronicle page — an inline edit. Every edit is a new attributed version; the edit destroys nothing and a revert is itself a new version. (delete_chronicle_page is the one operation that does destroy a record.) Pass the version you edited as expectedParentVersionId; a mismatch is STALE so two editors cannot silently lose each other's work. Provenance is recomputed: text that no longer matches what it was derived from becomes derived-then-edited, whatever the caller claims. origin is IGNORED on an edit — it is carried over from the parent version, because only a sweep may assert where text came from.
Body
projectIdstringrequiredpageIdstringrequiredexpectedParentVersionIdstringrequiredblocksArray<object>requiredidempotencyKeystringReturns
versionIdstringrequireddriftedArray<string>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "edit_chronicle_page",
"arguments": {
"projectId": "string",
"pageId": "string",
"expectedParentVersionId": "string",
"blocks": [
{
"id": "string",
"text": "string",
"citations": [],
"origin": null
}
],
"idempotencyKey": "string"
}
}
}const result = await client.callTool("edit_chronicle_page", {
"projectId": "string",
"pageId": "string",
"expectedParentVersionId": "string",
"blocks": [
{
"id": "string",
"text": "string",
"citations": [],
"origin": null
}
],
"idempotencyKey": "string"
});result = await session.call_tool("edit_chronicle_page", arguments={
"projectId": "string",
"pageId": "string",
"expectedParentVersionId": "string",
"blocks": [
{
"id": "string",
"text": "string",
"citations": [],
"origin": null
}
],
"idempotencyKey": "string"
}){
"projectId": "string",
"pageId": "string",
"expectedParentVersionId": "string",
"blocks": [
{
"id": "string",
"text": "string",
"citations": [],
"origin": null
}
],
"idempotencyKey": "string"
}{
"versionId": "string",
"drifted": [
"string"
]
}delete_chronicle_page
Destroy a Chronicle record: the page, every version of it, and every stored body. IRREVERSIBLE — this is the one operation on this surface that is not a new version, so nothing survives it and a revert cannot bring it back. Emptying a page by removing its blocks through edit_chronicle_page keeps the history and is what you usually want. Deleting a record a sweep generates does not stop the sweep: the next run recreates it from the same slug. Pass expectedHeadVersionId (the head you are looking at) to refuse with STALE if the record changed since you read it; omit it to delete unconditionally.
Parameters
projectIdstringrequiredargumentpageIdstringrequiredargumentexpectedHeadVersionIdstringargumentReturns
okbooleanrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "delete_chronicle_page",
"arguments": {
"projectId": "string",
"pageId": "string",
"expectedHeadVersionId": "string"
}
}
}const result = await client.callTool("delete_chronicle_page", {
"projectId": "string",
"pageId": "string",
"expectedHeadVersionId": "string"
});result = await session.call_tool("delete_chronicle_page", arguments={
"projectId": "string",
"pageId": "string",
"expectedHeadVersionId": "string"
}){
"ok": true
}add_follow_up
Add a directed, actionable follow-up (a directive) to a task, targeted at the current holder (lease owner/agent) — or, if unheld/lease-expired, the assignee. body is markdown. The target is notified (same path as @mentions); a follow-up you direct at yourself does not notify. Set reopen:true to send the work back: if the task is in REVIEW or DONE it transitions to READY (preserving its assignee) and emits task.reopened; from any other status the follow-up is recorded WITHOUT a transition (a no-op reopen). Distinct from add_comment (discussion).
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentbodystringrequiredargumentreopenbooleanargumentidempotencyKeystringargumentReturns
followUpobjectrequiredtaskobjectrequiredwarningsArray<string>{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "add_follow_up",
"arguments": {
"projectId": "string",
"taskId": "string",
"body": "string",
"reopen": true,
"idempotencyKey": "string"
}
}
}const result = await client.callTool("add_follow_up", {
"projectId": "string",
"taskId": "string",
"body": "string",
"reopen": true,
"idempotencyKey": "string"
});result = await session.call_tool("add_follow_up", arguments={
"projectId": "string",
"taskId": "string",
"body": "string",
"reopen": true,
"idempotencyKey": "string"
}){
"followUp": {
"id": "string",
"taskId": "string",
"projectId": "string",
"body": "string",
"author": "string",
"target": "string",
"status": "open",
"reopened": true,
"ts": "string"
},
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"warnings": [
"string"
]
}ack_follow_up
Acknowledge a follow-up directive: flips its status to acked (it then drops out of get_task_context.openFollowUps). Only the follow-up’s TARGET (the directed holder/assignee) — or a project admin — may ack it, so a writer can’t silently suppress a directive aimed at someone else. (An untargeted follow-up may be acked by any writer.) The holder calls this once it has acted on the directive.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentfollowUpIdstringrequiredargumentReturns
followUpobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "ack_follow_up",
"arguments": {
"projectId": "string",
"taskId": "string",
"followUpId": "string"
}
}
}const result = await client.callTool("ack_follow_up", {
"projectId": "string",
"taskId": "string",
"followUpId": "string"
});result = await session.call_tool("ack_follow_up", arguments={
"projectId": "string",
"taskId": "string",
"followUpId": "string"
}){
"followUp": {
"id": "string",
"taskId": "string",
"projectId": "string",
"body": "string",
"author": "string",
"target": "string",
"status": "open",
"reopened": true,
"ts": "string"
}
}list_follow_ups
List a task’s follow-ups oldest-first (chronological). Set openOnly to return only outstanding (status=open) directives.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentopenOnlybooleanargumentlimitinteger<= 200argumentcursorstringargumentReturns
followUpsArray<object>requirednextCursorstring{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_follow_ups",
"arguments": {
"projectId": "string",
"taskId": "string",
"openOnly": true,
"limit": 0,
"cursor": "string"
}
}
}const result = await client.callTool("list_follow_ups", {
"projectId": "string",
"taskId": "string",
"openOnly": true,
"limit": 0,
"cursor": "string"
});result = await session.call_tool("list_follow_ups", arguments={
"projectId": "string",
"taskId": "string",
"openOnly": true,
"limit": 0,
"cursor": "string"
}){
"followUps": [
{
"id": "string",
"taskId": "string",
"projectId": "string",
"body": "string",
"author": "string",
"target": "string",
"status": "open",
"reopened": true,
"ts": "string"
}
],
"nextCursor": "string"
}start_runs
Open a RACE on a task: create N competing run children (each a real claimable task) and put the parent in racing mode so it drops out of the claim pool. Each run copies the parent’s brief / labels / capabilities / customFields; pass count for anonymous runs, or agents to route each run to a named agent (its length wins over count), and brief to override the run description. Workers claim/work/complete a run exactly like any task; later call pick_run to promote the winner. The parent must be able to ENTER work — i.e. its status has a legal edge to IN_PROGRESS (READY on the default table, so a BACKLOG task must be readied first) — or already IN_PROGRESS held by you. Capped by the project’s maxRunsPerTask.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentversioninteger[0, 9007199254740991]requiredargumentcountinteger<= 50argumentagentsArray<string>argumentbriefstringargumentidempotencyKeystringargumentReturns
taskobjectrequiredrunsArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "start_runs",
"arguments": {
"projectId": "string",
"taskId": "string",
"version": 0,
"count": 0,
"agents": [
"string"
],
"brief": "string",
"idempotencyKey": "string"
}
}
}const result = await client.callTool("start_runs", {
"projectId": "string",
"taskId": "string",
"version": 0,
"count": 0,
"agents": [
"string"
],
"brief": "string",
"idempotencyKey": "string"
});result = await session.call_tool("start_runs", arguments={
"projectId": "string",
"taskId": "string",
"version": 0,
"count": 0,
"agents": [
"string"
],
"brief": "string",
"idempotencyKey": "string"
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"runs": [
{
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
]
}open_run
Add ONE more run to an already-racing task (let another agent join the race late). The parent must already be racing (start it with start_runs). Pass agent to route the new run, brief to override its description. Honors the project’s maxRunsPerTask cap.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentagentstringargumentbriefstringargumentidempotencyKeystringargumentReturns
runobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "open_run",
"arguments": {
"projectId": "string",
"taskId": "string",
"agent": "string",
"brief": "string",
"idempotencyKey": "string"
}
}
}const result = await client.callTool("open_run", {
"projectId": "string",
"taskId": "string",
"agent": "string",
"brief": "string",
"idempotencyKey": "string"
});result = await session.call_tool("open_run", arguments={
"projectId": "string",
"taskId": "string",
"agent": "string",
"brief": "string",
"idempotencyKey": "string"
}){
"run": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
}list_runs
List the run children of a racing task — one bounded query. Each entry is a compact RunView { taskId, status, assignee, leaseHolder, hasDeliverable, deliverable?, version }. Use it to see who is racing and which runs are submitted (hasDeliverable) before calling pick_run.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentReturns
runsArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_runs",
"arguments": {
"projectId": "string",
"taskId": "string"
}
}
}const result = await client.callTool("list_runs", {
"projectId": "string",
"taskId": "string"
});result = await session.call_tool("list_runs", arguments={
"projectId": "string",
"taskId": "string"
}){
"runs": [
{
"taskId": "string",
"status": "BACKLOG",
"assignee": "string",
"leaseHolder": "string",
"hasDeliverable": true,
"deliverable": "string",
"version": 0
}
]
}pick_run
Pick the WINNING run of a race: promote its deliverable onto the parent, advance the parent (→ REVIEW if the board reviews, else DONE), and CANCEL every other run (releasing any lease). The chosen run must belong to this parent (runOf) and be submitted (REVIEW/DONE with a deliverable) — else INVALID_TRANSITION. If the parent reaches DONE its dependents auto-unblock. Version-checked on the parent.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentrunTaskIdstringrequiredargumentversioninteger[0, 9007199254740991]requiredargumentReturns
taskobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "pick_run",
"arguments": {
"projectId": "string",
"taskId": "string",
"runTaskId": "string",
"version": 0
}
}
}const result = await client.callTool("pick_run", {
"projectId": "string",
"taskId": "string",
"runTaskId": "string",
"version": 0
});result = await session.call_tool("pick_run", arguments={
"projectId": "string",
"taskId": "string",
"runTaskId": "string",
"version": 0
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
}cancel_runs
Abandon a race without picking a winner: take the parent out of racing mode (→ READY, back into the pool) and CANCEL all its runs. Version-checked on the parent.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentversioninteger[0, 9007199254740991]requiredargumentReturns
taskobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "cancel_runs",
"arguments": {
"projectId": "string",
"taskId": "string",
"version": 0
}
}
}const result = await client.callTool("cancel_runs", {
"projectId": "string",
"taskId": "string",
"version": 0
});result = await session.call_tool("cancel_runs", arguments={
"projectId": "string",
"taskId": "string",
"version": 0
}){
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
}
}list_notifications
Your in-app notifications, newest-first across all projects (T-33), with read state and the task/actor to deep-link. Also returns your total unread count (for a badge). Paginate with cursor.
Parameters
limitinteger<= 200argumentcursorstringargumentReturns
notificationsArray<object>requiredunreadinteger[0, 9007199254740991]requirednextCursorstring{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_notifications",
"arguments": {
"limit": 0,
"cursor": "string"
}
}
}const result = await client.callTool("list_notifications", {
"limit": 0,
"cursor": "string"
});result = await session.call_tool("list_notifications", arguments={
"limit": 0,
"cursor": "string"
}){
"notifications": [
{
"id": "string",
"identityId": "string",
"projectId": "string",
"boardId": "string",
"type": "mention",
"taskId": "string",
"actor": "string",
"read": true,
"ts": "string"
}
],
"unread": 0,
"nextCursor": "string"
}mark_notifications_read
Mark your notifications read: pass ids for specific ones, or all:true to clear every unread.
Parameters
idsArray<string>argumentallbooleanargumentReturns
updatedinteger[0, 9007199254740991]required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "mark_notifications_read",
"arguments": {
"ids": [
"string"
],
"all": true
}
}
}const result = await client.callTool("mark_notifications_read", {
"ids": [
"string"
],
"all": true
});result = await session.call_tool("mark_notifications_read", arguments={
"ids": [
"string"
],
"all": true
}){
"updated": 0
}attach_file
Create a pending attachment and return a V4 signed PUT URL for upload.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentfileNamestringrequiredargumentmimeTypestringrequiredargumentkindstringfileimageaudiovideorequiredargumentbytesinteger[0, 9007199254740991]requiredargumentidempotencyKeystringargumentReturns
attachmentIdstringrequireduploadUrlstringrequiredexpiresAtstringrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "attach_file",
"arguments": {
"projectId": "string",
"taskId": "string",
"fileName": "string",
"mimeType": "string",
"kind": "file",
"bytes": 0,
"idempotencyKey": "string"
}
}
}const result = await client.callTool("attach_file", {
"projectId": "string",
"taskId": "string",
"fileName": "string",
"mimeType": "string",
"kind": "file",
"bytes": 0,
"idempotencyKey": "string"
});result = await session.call_tool("attach_file", arguments={
"projectId": "string",
"taskId": "string",
"fileName": "string",
"mimeType": "string",
"kind": "file",
"bytes": 0,
"idempotencyKey": "string"
}){
"attachmentId": "string",
"uploadUrl": "string",
"expiresAt": "string"
}list_attachments
List a task’s attachments with signed download URLs (manifest; never raw bytes).
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentReturns
attachmentsArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_attachments",
"arguments": {
"projectId": "string",
"taskId": "string"
}
}
}const result = await client.callTool("list_attachments", {
"projectId": "string",
"taskId": "string"
});result = await session.call_tool("list_attachments", arguments={
"projectId": "string",
"taskId": "string"
}){
"attachments": [
{
"id": "string",
"taskId": "string",
"gcsUri": "string",
"mimeType": "string",
"bytes": 0,
"sha256": "string",
"thumbUri": "string",
"extractedText": "string",
"kind": "file",
"status": "pending",
"createdBy": "string",
"ts": "string",
"downloadUrl": "string"
}
]
}search_tasks
Search tasks. mode=keyword matches literal terms; mode=semantic matches by MEANING over embedded summaries, deliverables, notes, memory and attachment text ("how did we handle the OAuth refresh edge case" finds the task even if it never used those words); mode=hybrid (default) fuses both. Falls back to keyword when the workspace has embeddings disabled — the answer says which ran.
Parameters
projectIdstringrequiredargumentquerystringrequiredargumentboardIdstringargumentkinteger<= 50argumentmodestringkeywordsemantichybridargumentReturns
resultsArray<object>requiredmodestringkeywordsemantichybridrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "search_tasks",
"arguments": {
"projectId": "string",
"query": "string",
"boardId": "string",
"k": 0,
"mode": "keyword"
}
}
}const result = await client.callTool("search_tasks", {
"projectId": "string",
"query": "string",
"boardId": "string",
"k": 0,
"mode": "keyword"
});result = await session.call_tool("search_tasks", arguments={
"projectId": "string",
"query": "string",
"boardId": "string",
"k": 0,
"mode": "keyword"
}){
"results": [
{
"task": {
"id": "string",
"projectId": "string",
"boardId": "string",
"columnId": "string",
"title": "string",
"description": "string",
"status": "BACKLOG",
"priority": "low",
"assignee": "string",
"labels": [
"string"
],
"requiredCapabilities": [
"string"
],
"estimate": 0,
"dueAt": "string",
"parentTaskId": "string",
"order": "string",
"customFields": {},
"blockedBy": [
"string"
],
"blocks": [
"string"
],
"contextSummary": "string",
"deliverable": "string",
"artifacts": [
{
"kind": "pr",
"url": "string",
"ref": "string",
"repo": "string",
"title": "string",
"addedBy": "string",
"addedByAgent": "string",
"ts": "string",
"retracted": {
"by": "string",
"byAgent": "string",
"at": "string",
"reason": "string"
}
}
],
"chars": {
"doc": 0,
"items": 0,
"comments": 0,
"followups": 0,
"memory": 0,
"attachments": 0,
"total": 0
},
"reviewer": "string",
"review": {
"reviewer": "string",
"completedBy": "string",
"completedByAgent": "string",
"requestedBy": "string",
"requestedAt": "string"
},
"modelHint": {
"model": "string",
"effort": "low",
"rationale": "string"
},
"runOf": "string",
"sprintIds": [
"string"
],
"racing": true,
"raceOpenedAt": "string",
"claimAttempts": 0,
"nextEligibleAt": "string",
"version": 0,
"claim": {
"owner": "string",
"agent": "string",
"agentId": "string",
"expiresAt": "string"
},
"createdBy": "string",
"createdByAgent": "string",
"createdAt": "string",
"updatedBy": "string",
"updatedAt": "string"
},
"score": 0
}
],
"mode": "keyword"
}list_agent_sessions
List the CALLER'S OWN recent MCP agent sessions (connection telemetry): which of your agents/clients connected, when, with what client, and how active. Scoped to the caller — never returns other users' sessions; workspace admins may pass all:true for the workspace-wide view. Takes no projectId: this is workspace-scoped telemetry, and a member's capability grant is read from get_project (T-145). wakeBound: false on a row means that agent's wake (push) subscription is consumable by NOBODY — its IAM binding failed to land, so it gets no doorbell until it reconnects; absent means no wake channel was provisioned (the default) and is not a fault.
Parameters
limitinteger<= 200argumentallbooleanargumentReturns
sessionsArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_agent_sessions",
"arguments": {
"limit": 0,
"all": true
}
}
}const result = await client.callTool("list_agent_sessions", {
"limit": 0,
"all": true
});result = await session.call_tool("list_agent_sessions", arguments={
"limit": 0,
"all": true
}){
"sessions": [
{
"sessionKey": "string",
"agentId": "string",
"agentName": "string",
"identityId": "string",
"kind": "service_account",
"clientName": "string",
"clientVersion": "string",
"oauthClientFp": "string",
"firstSeenAt": "string",
"lastSeenAt": "string",
"sessionCount": 0,
"requestCount": 0,
"toolCounts": {},
"lastTool": "string",
"lastProjectId": "string",
"revokedAt": "string",
"revokedBy": "string",
"wakeBound": true,
"identityDisplay": "string"
}
]
}prune_agent_sessions
Delete stale agent-session rows (ghosts) idle longer than olderThanMinutes (default 60). Scoped to the CALLER'S OWN sessions; workspace admins may pass all:true to prune across the workspace. Returns the count deleted. Active/online agents (recently seen) are never affected, and DISCONNECTED rows are kept (prune must not un-revoke). SIDE EFFECT: deleting a row also attempts to delete that agent's wake (push) subscription — the row is the only copy of its id — so a pruned agent gets a fresh one on its next handshake. That delete is BEST-EFFORT: wakeRevokeFailed counts the ones that did not land (absent = there were none to delete, 0 = all landed). Each failure leaves a subscription nothing can name again — with roles/pubsub.subscriber still bound to that agent — until its 7-day expiry; the core log line wake_revoke_failed carries the subscription names to delete by hand.
Parameters
olderThanMinutesinteger<= 43200argumentallbooleanargumentReturns
prunednumberrequiredwakeRevokeFailednumber{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "prune_agent_sessions",
"arguments": {
"olderThanMinutes": 0,
"all": true
}
}
}const result = await client.callTool("prune_agent_sessions", {
"olderThanMinutes": 0,
"all": true
});result = await session.call_tool("prune_agent_sessions", arguments={
"olderThanMinutes": 0,
"all": true
}){
"pruned": 0,
"wakeRevokeFailed": 0
}disconnect_agent
Disconnect ONE agent session (per-session, not the whole identity): the core rejects that (identityId, agentId)'s subsequent tool calls (~60s to take effect). The revocation is STICKY — it is not auto-cleared and is not removed by prune — so to reconnect the agent must use a NEW agent id (rotate/reset it); disconnecting your OWN active session is unrecoverable from the agent side. CAUTION: an agentId of the form id:<email> is the handle for callers that send no agent header — which includes that person's WEB UI, so disconnecting it locks them out of the web app too, stickily. Per-agent sessions (their own agentId) do not have this effect. You may disconnect your OWN agents; workspace admins may disconnect anyone's. Returns wakeRevoked when the agent had a wake subscription: false means the session IS disconnected but its Pub/Sub subscription survived the delete — retry or delete it by hand. NOTE: this is best-effort against cooperative clients (the agent id is a client-asserted header, so an agent can reconnect under a NEW id) — for a hard, unspoofable block on TOOL CALLS use Disable owner (account-disable), which rejects every agent acting as that identity (the identity comes from the verified bearer, so no header can dodge it) and stops it minting any new wake credential. Since T-485 the two controls ARE nested: a deny from decide_access also runs this tool's revoke over every live agent session it can enumerate — sessions marked disconnected, wake subscriptions deleted (best-effort; the web fallback id:<identityId> session keeps its row and loses only its subscription). The doorbell carries no board data anyway (empty body; agent + kind attributes only) and every tool call behind it is refused.
Parameters
identityIdstringrequiredargumentagentIdstringrequiredargumentReturns
disconnectedbooleanrequiredwakeRevokedboolean{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "disconnect_agent",
"arguments": {
"identityId": "string",
"agentId": "string"
}
}
}const result = await client.callTool("disconnect_agent", {
"identityId": "string",
"agentId": "string"
});result = await session.call_tool("disconnect_agent", arguments={
"identityId": "string",
"agentId": "string"
}){
"disconnected": true,
"wakeRevoked": true
}register_agent_profile
Register (or update) the CALLER's durable capability profile in a project (T-3) — what this agent CAN do, server-side, so selection stops relying on a capability list re-declared on every poll. Keyed by your agent NAME (the x-batondeck-agent header; falls back to your identity). capabilities is your skill set; tags are broad coverage areas (a tag can cover a required capability as a weaker, penalized match). maxConcurrency becomes the default claim ceiling for claim_next. Then pass useProfile:true to next_task/claim_next/rank_tasks to match against this profile (exact · alias · tag) and get an outcome-weighted reliability nudge. Re-registering overwrites in place.
Parameters
projectIdstringrequiredargumentcapabilitiesArray<string>requiredargumenttagsArray<string>argumentmaxConcurrencyinteger<= 1000argumentcostClassstringlowstandardpremiumargumentReturns
profileobjectrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "register_agent_profile",
"arguments": {
"projectId": "string",
"capabilities": [
"string"
],
"tags": [
"string"
],
"maxConcurrency": 0,
"costClass": "low"
}
}
}const result = await client.callTool("register_agent_profile", {
"projectId": "string",
"capabilities": [
"string"
],
"tags": [
"string"
],
"maxConcurrency": 0,
"costClass": "low"
});result = await session.call_tool("register_agent_profile", arguments={
"projectId": "string",
"capabilities": [
"string"
],
"tags": [
"string"
],
"maxConcurrency": 0,
"costClass": "low"
}){
"profile": {
"agentKey": "string",
"projectId": "string",
"capabilities": [
"string"
],
"tags": [
"string"
],
"maxConcurrency": 0,
"costClass": "low",
"createdBy": "string",
"createdAt": "string",
"updatedAt": "string"
}
}get_skill_stats
An agent's capability profile + its outcome-weighted skill stats in a project (T-3) — per (agent, capability): completions, blocks, a regularized reliability (completion rate with a low-sample floor, so new agents read as neutral, never 0), and a mean handle time. Powers the Agents view ("96% completion on playwright, ~4m each"). Omit agentKey for your OWN stats; pass one (any project member) to inspect another agent.
Parameters
projectIdstringrequiredargumentagentKeystringargumentReturns
profileobject | nullrequiredstatsArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_skill_stats",
"arguments": {
"projectId": "string",
"agentKey": "string"
}
}
}const result = await client.callTool("get_skill_stats", {
"projectId": "string",
"agentKey": "string"
});result = await session.call_tool("get_skill_stats", arguments={
"projectId": "string",
"agentKey": "string"
}){
"profile": {
"agentKey": "string",
"projectId": "string",
"capabilities": [
"string"
],
"tags": [
"string"
],
"maxConcurrency": 0,
"costClass": "low",
"createdBy": "string",
"createdAt": "string",
"updatedAt": "string"
},
"stats": [
{
"agentKey": "string",
"projectId": "string",
"capability": "string",
"completed": 0,
"blocked": 0,
"totalMs": 0,
"samples": 0,
"reliability": 0,
"meanMs": 0,
"updatedAt": "string"
}
]
}check_access
Check workspace access status for an identity (self-only; admins may check anyone).
Parameters
emailstringrequiredargumentReturns
statusstringadminapprovedpendingdeniednonerequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "check_access",
"arguments": {
"email": "string"
}
}
}const result = await client.callTool("check_access", {
"email": "string"
});result = await session.call_tool("check_access", arguments={
"email": "string"
}){
"status": "admin"
}request_access
Register/request workspace access (self-only, idempotent). In auto mode the account is approved immediately (and rostered so an admin can later disable it); in approval mode it creates a pending request an admin must approve.
Parameters
emailstringrequiredargumentReturns
statusstringadminapprovedpendingdeniednonerequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "request_access",
"arguments": {
"email": "string"
}
}
}const result = await client.callTool("request_access", {
"email": "string"
});result = await session.call_tool("request_access", arguments={
"email": "string"
}){
"status": "admin"
}get_feature_flags
Read the workspace runtime feature flags. subscriptionsEnabled is the master switch for plan usage-limits + the billing UI — when false, everyone uses the workspace without limits. ui holds admin-tuned web/UI setting overrides (search size, poll intervals, …). analytics holds the analytics runtime overrides (rateLimitPerMin, maxQueryDays) — the analytics service polls this tool, so it is the read path for its enforcement. Readable by ANY authenticated identity — these are workspace-wide switches, not project data, and the web app reads them before a project is chosen (this tool takes no projectId, so there is no membership to check). impersonationEnabled is the master switch for accepting a WorkOS-impersonated sign-in on the HUMAN (web) leg — it defaults to FALSE when unset (fail closed), gates ACCEPTANCE only (a refused impersonation is still logged and audited), and does NOT govern the agent/MCP leg, which refuses impersonation unconditionally. Read-only here; workspace admins change them.
Returns
subscriptionsEnabledbooleanrequireduiobjectrequiredanalyticsobjectrequiredoncallobjectrequiredimpersonationEnabledbooleanrequiredheadlessIntegrationConfigEnabledbooleanrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_feature_flags",
"arguments": {}
}
}const result = await client.callTool("get_feature_flags", {});result = await session.call_tool("get_feature_flags", arguments={}){
"subscriptionsEnabled": true,
"ui": {
"searchK": 1,
"searchDebounceMs": 0,
"taskPageLimit": 1,
"recentActivityLimit": 1,
"sessionsLimit": 1,
"sessionsPollMs": 2000,
"presenceOfflineAfterMs": 5000,
"presenceWindowMs": 60000,
"linkFavicons": true
},
"analytics": {
"rateLimitPerMin": 0,
"maxQueryDays": 1
},
"oncall": {
"rawRetentionDays": 1,
"deliveryRetentionDays": 1,
"coalesceWindowSec": 0,
"broadcastCap": 1
},
"impersonationEnabled": true,
"headlessIntegrationConfigEnabled": true
}get_my_settings
Your own preferences (not a project's). Returns your ticket-token ceiling and enforcement rung, or null for each where you have set none, plus the defaults that apply when unset. Token counts throughout BatonDeck are APPROXIMATE — estimated from text length at roughly 4 characters per token — and will not match a model's exact tokenizer.
Returns
maxTicketTokensinteger | nullrequiredtokenPolicystring | nullrequireddefaultsobjectrequiredestimatedbooleanrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_my_settings",
"arguments": {}
}
}const result = await client.callTool("get_my_settings", {});result = await session.call_tool("get_my_settings", arguments={}){
"maxTicketTokens": -9007199254740991,
"tokenPolicy": "off",
"defaults": {
"maxTicketTokens": -9007199254740991,
"tokenPolicy": "off",
"charsPerToken": 0
},
"estimated": true
}set_my_settings
Set YOUR OWN preferences. null clears a preference and returns you to the project default. Both settings can only ever make things STRICTER for you: a ceiling below a project's is honoured, one above it is ignored; an enforcement rung stricter than the project's is honoured, a looser one is not. So this cannot be used to opt out of a project's governance. Counts are approximate — leave headroom rather than setting your ceiling to a real context window size.
Body
maxTicketTokensinteger | nulltokenPolicystring | nullReturns
maxTicketTokensinteger | nullrequiredtokenPolicystring | nullrequireddefaultsobjectrequiredestimatedbooleanrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "set_my_settings",
"arguments": {
"maxTicketTokens": 100,
"tokenPolicy": "off"
}
}
}const result = await client.callTool("set_my_settings", {
"maxTicketTokens": 100,
"tokenPolicy": "off"
});result = await session.call_tool("set_my_settings", arguments={
"maxTicketTokens": 100,
"tokenPolicy": "off"
}){
"maxTicketTokens": 100,
"tokenPolicy": "off"
}{
"maxTicketTokens": -9007199254740991,
"tokenPolicy": "off",
"defaults": {
"maxTicketTokens": -9007199254740991,
"tokenPolicy": "off",
"charsPerToken": 0
},
"estimated": true
}create_cli_token
Mint a CLI token for YOURSELF — a bd_… credential to export as BATONDECK_TOKEN and use against the MCP endpoint from a terminal. It carries your own tenant and permissions and cannot exceed them. THE VALUE IS RETURNED ONCE AND NEVER AGAIN: store it now, or revoke it and mint another. Short-lived by default; revoke with revoke_cli_token.
Parameters
labelstringrequiredargumentttlSecondsinteger[60, 604800]argumentReturns
idstringrequiredlabelstringrequiredcreatedAtstringrequiredexpiresAtstringrequiredlastUsedAtstring | nullrequiredrevokedAtstring | nullrequiredstatusstringliveexpiredrevokedrequiredtokenstringrequiredshownOncebooleanrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "create_cli_token",
"arguments": {
"label": "string",
"ttlSeconds": 60
}
}
}const result = await client.callTool("create_cli_token", {
"label": "string",
"ttlSeconds": 60
});result = await session.call_tool("create_cli_token", arguments={
"label": "string",
"ttlSeconds": 60
}){
"id": "string",
"label": "string",
"createdAt": "string",
"expiresAt": "string",
"lastUsedAt": "string",
"revokedAt": "string",
"status": "live",
"token": "string",
"shownOnce": true
}list_cli_tokens
Your own CLI tokens: label, when minted, when it expires, when it was last used, and whether it is live, expired or revoked. The token VALUES are not returned — they are shown once, at mint time, and nothing can retrieve them afterwards.
Returns
tokensArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_cli_tokens",
"arguments": {}
}
}const result = await client.callTool("list_cli_tokens", {});result = await session.call_tool("list_cli_tokens", arguments={}){
"tokens": [
{
"id": "string",
"label": "string",
"createdAt": "string",
"expiresAt": "string",
"lastUsedAt": "string",
"revokedAt": "string",
"status": "live"
}
]
}revoke_cli_token
Revoke one of YOUR OWN CLI tokens by its id, as returned by list_cli_tokens. The token stops working on its next use — nothing is cached. Idempotent: revoking an already revoked or unknown token returns revoked:false rather than an error.
Parameters
idstringrequiredargumentReturns
revokedbooleanrequiredtokenobject | nullrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "revoke_cli_token",
"arguments": {
"id": "string"
}
}
}const result = await client.callTool("revoke_cli_token", {
"id": "string"
});result = await session.call_tool("revoke_cli_token", arguments={
"id": "string"
}){
"revoked": true,
"token": {
"id": "string",
"label": "string",
"createdAt": "string",
"expiresAt": "string",
"lastUsedAt": "string",
"revokedAt": "string",
"status": "live"
}
}detect_anomalies
Health sensor for stuck work (T-10): report the failure shapes a board is currently exhibiting, computed lazily from one bounded board page plus the recent-events tail (no background sweeper). Detects stale_in_progress (a LIVE lease heartbeating with no progress — the shape the dead-lease reaper structurally cannot see), claim_thrash, rework_loop (REVIEW ping-pong), wip_jam (every IN_PROGRESS lease expired and nothing rescanning → call reap_stale_leases), starved_urgent and dep_deadlock (blocked behind a CANCELLED/DEAD_LETTER blocker that can never auto-unblock). Poll this to self-correct instead of waiting for a human to notice. Pass boardId (or taskId, whose board is resolved for you); kinds filters, sinceSec sets the event lookback. Appends a task.anomaly_detected event on the FIRST sighting of each (kind, task) so the finding lands in the activity log — which is why this needs a write role.
Parameters
projectIdstringrequiredargumentboardIdstringargumenttaskIdstringargumentkindsArray<string>stale_in_progressclaim_thrashrework_loopwip_jamstarved_urgentdep_deadlockargumentsinceSecinteger<= 2592000argumentReturns
anomaliesArray<object>requiredscannedobjectrequiredboardIdstringrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "detect_anomalies",
"arguments": {
"projectId": "string",
"boardId": "string",
"taskId": "string",
"kinds": [
"stale_in_progress"
],
"sinceSec": 0
}
}
}const result = await client.callTool("detect_anomalies", {
"projectId": "string",
"boardId": "string",
"taskId": "string",
"kinds": [
"stale_in_progress"
],
"sinceSec": 0
});result = await session.call_tool("detect_anomalies", arguments={
"projectId": "string",
"boardId": "string",
"taskId": "string",
"kinds": [
"stale_in_progress"
],
"sinceSec": 0
}){
"anomalies": [
{
"kind": "stale_in_progress",
"taskId": "string",
"severity": "warn",
"evidence": {},
"detectedAt": "string"
}
],
"scanned": {
"tasksScanned": 0,
"eventsScanned": 0,
"windowFrom": "string",
"oldestEventTs": "string",
"truncated": true
},
"boardId": "string"
}create_integration
Create an on-call webhook integration: a signed endpoint a telemetry source (Grafana, Alertmanager, Dynatrace, PagerDuty, Datadog, Google Cloud Monitoring, or a generic signed JSON sender) posts alerts to. The auth mode a source can present is NOT a free choice — most cannot compute our signature and Cloud Monitoring cannot even set a header, so it uses basic (HTTP Basic, the key as the password). Each delivery becomes or updates an incident ticket on the target board and is routed to an on-call agent or broadcast to live agents. For hmac mode the server MINTS the secret and returns it EXACTLY ONCE (secret, with shownOnce:true) — store it immediately; it is never retrievable again. Pass externalSecret instead when the source issues its own (PagerDuty). Paste the returned endpointPath into the source's webhook configuration.
Body
projectIdstringrequirednamestringrequiredsourcestringgenericgrafanaalertmanagerdynatracedatadoggcppagerdutywebhookrequiredboardIdstringrequiredcolumnIdstringauthobjectrequiredmappingobjectroutingobjectrequiredlifecyclestringcorrelateautocloseper_eventpolicyobjectlimitsobjectidempotencyKeystringReturns
integrationobjectrequiredsecretstringshownOnceboolean{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "create_integration",
"arguments": {
"projectId": "string",
"name": "string",
"source": "generic",
"boardId": "string",
"columnId": "string",
"auth": {
"mode": "hmac",
"externalSecret": "string",
"apiKey": "string",
"jwt": {
"issuer": "https://example.com",
"audience": "string",
"jwksUrl": "https://example.com"
}
},
"mapping": {
"severityMap": {},
"labels": [
"string"
],
"annotationAllowlist": [
"string"
],
"titleTemplate": "string",
"extract": {
"events": "string",
"fingerprint": [
"string"
],
"title": [
"string"
],
"severity": [
"string"
],
"status": [
"string"
],
"description": [
"string"
],
"occurredAt": [
"string"
],
"labels": "string",
"annotations": "string",
"links": [
"string"
],
"dedupKey": [
"string"
]
},
"statusMap": {},
"defaults": {
"title": "string",
"severity": "string",
"status": "firing"
}
},
"routing": {
"mode": "oncall",
"onCallAgent": "string",
"fallbackBroadcast": true,
"pdUserMap": {}
},
"lifecycle": "correlate",
"policy": {
"openPrForReview": true,
"autoReviewCycle": true,
"reviewerAgent": "string"
},
"limits": {
"ratePerMin": 1,
"payloadCapKb": 1
},
"idempotencyKey": "string"
}
}
}const result = await client.callTool("create_integration", {
"projectId": "string",
"name": "string",
"source": "generic",
"boardId": "string",
"columnId": "string",
"auth": {
"mode": "hmac",
"externalSecret": "string",
"apiKey": "string",
"jwt": {
"issuer": "https://example.com",
"audience": "string",
"jwksUrl": "https://example.com"
}
},
"mapping": {
"severityMap": {},
"labels": [
"string"
],
"annotationAllowlist": [
"string"
],
"titleTemplate": "string",
"extract": {
"events": "string",
"fingerprint": [
"string"
],
"title": [
"string"
],
"severity": [
"string"
],
"status": [
"string"
],
"description": [
"string"
],
"occurredAt": [
"string"
],
"labels": "string",
"annotations": "string",
"links": [
"string"
],
"dedupKey": [
"string"
]
},
"statusMap": {},
"defaults": {
"title": "string",
"severity": "string",
"status": "firing"
}
},
"routing": {
"mode": "oncall",
"onCallAgent": "string",
"fallbackBroadcast": true,
"pdUserMap": {}
},
"lifecycle": "correlate",
"policy": {
"openPrForReview": true,
"autoReviewCycle": true,
"reviewerAgent": "string"
},
"limits": {
"ratePerMin": 1,
"payloadCapKb": 1
},
"idempotencyKey": "string"
});result = await session.call_tool("create_integration", arguments={
"projectId": "string",
"name": "string",
"source": "generic",
"boardId": "string",
"columnId": "string",
"auth": {
"mode": "hmac",
"externalSecret": "string",
"apiKey": "string",
"jwt": {
"issuer": "https://example.com",
"audience": "string",
"jwksUrl": "https://example.com"
}
},
"mapping": {
"severityMap": {},
"labels": [
"string"
],
"annotationAllowlist": [
"string"
],
"titleTemplate": "string",
"extract": {
"events": "string",
"fingerprint": [
"string"
],
"title": [
"string"
],
"severity": [
"string"
],
"status": [
"string"
],
"description": [
"string"
],
"occurredAt": [
"string"
],
"labels": "string",
"annotations": "string",
"links": [
"string"
],
"dedupKey": [
"string"
]
},
"statusMap": {},
"defaults": {
"title": "string",
"severity": "string",
"status": "firing"
}
},
"routing": {
"mode": "oncall",
"onCallAgent": "string",
"fallbackBroadcast": true,
"pdUserMap": {}
},
"lifecycle": "correlate",
"policy": {
"openPrForReview": true,
"autoReviewCycle": true,
"reviewerAgent": "string"
},
"limits": {
"ratePerMin": 1,
"payloadCapKb": 1
},
"idempotencyKey": "string"
}){
"projectId": "string",
"name": "string",
"source": "generic",
"boardId": "string",
"columnId": "string",
"auth": {
"mode": "hmac",
"externalSecret": "string",
"apiKey": "string",
"jwt": {
"issuer": "https://example.com",
"audience": "string",
"jwksUrl": "https://example.com"
}
},
"mapping": {
"severityMap": {},
"labels": [
"string"
],
"annotationAllowlist": [
"string"
],
"titleTemplate": "string",
"extract": {
"events": "string",
"fingerprint": [
"string"
],
"title": [
"string"
],
"severity": [
"string"
],
"status": [
"string"
],
"description": [
"string"
],
"occurredAt": [
"string"
],
"labels": "string",
"annotations": "string",
"links": [
"string"
],
"dedupKey": [
"string"
]
},
"statusMap": {},
"defaults": {
"title": "string",
"severity": "string",
"status": "firing"
}
},
"routing": {
"mode": "oncall",
"onCallAgent": "string",
"fallbackBroadcast": true,
"pdUserMap": {}
},
"lifecycle": "correlate",
"policy": {
"openPrForReview": true,
"autoReviewCycle": true,
"reviewerAgent": "string"
},
"limits": {
"ratePerMin": 1,
"payloadCapKb": 1
},
"idempotencyKey": "string"
}{
"integration": {
"id": "string",
"projectId": "string",
"name": "string",
"source": "generic",
"enabled": true,
"target": {
"boardId": "string",
"columnId": "string"
},
"auth": {
"mode": "hmac",
"rotatedAt": "string",
"externallyIssued": true,
"dualAcceptUntil": "string"
},
"mapping": {},
"routing": {},
"lifecycle": "correlate",
"policy": {},
"limits": {
"ratePerMin": 0,
"payloadCapKb": 0
},
"version": 0,
"createdBy": "string",
"createdAt": "string",
"updatedAt": "string",
"endpointPath": "string",
"deletedAt": "string"
},
"secret": "string",
"shownOnce": true
}update_integration
Update an on-call integration: enable/disable it, retarget its board/column, change the severity mapping, routing (on-call agent, broadcast, PagerDuty-upstream, fallback), lifecycle mode, policy toggles or limits. Version-checked (STALE on mismatch). Auth MATERIAL is not changed here — use rotate_integration_secret; auth.mode may be switched, which requires the new mode's material and mints a fresh secret for hmac.
Body
projectIdstringrequiredintegrationIdstringrequiredversioninteger[1, 9007199254740991]requiredpatchobjectrequiredReturns
integrationobjectrequiredsecretstringshownOnceboolean{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "update_integration",
"arguments": {
"projectId": "string",
"integrationId": "string",
"version": 1,
"patch": {
"name": "string",
"enabled": true,
"boardId": "string",
"columnId": "string",
"auth": {
"mode": "hmac",
"externalSecret": "string",
"apiKey": "string",
"jwt": {
"issuer": "https://example.com",
"audience": "string",
"jwksUrl": "https://example.com"
}
},
"mapping": {
"severityMap": {},
"labels": [
"string"
],
"annotationAllowlist": [
"string"
],
"titleTemplate": "string",
"extract": {
"events": "string",
"fingerprint": [
"string"
],
"title": [
"string"
],
"severity": [
"string"
],
"status": [
"string"
],
"description": [
"string"
],
"occurredAt": [
"string"
],
"labels": "string",
"annotations": "string",
"links": [
"string"
],
"dedupKey": [
"string"
]
},
"statusMap": {},
"defaults": {
"title": "string",
"severity": "string",
"status": "firing"
}
},
"routing": {
"mode": "oncall",
"onCallAgent": "string",
"fallbackBroadcast": true,
"pdUserMap": {}
},
"lifecycle": "correlate",
"policy": {
"openPrForReview": true,
"autoReviewCycle": true,
"reviewerAgent": "string"
},
"limits": {
"ratePerMin": 1,
"payloadCapKb": 1
}
}
}
}
}const result = await client.callTool("update_integration", {
"projectId": "string",
"integrationId": "string",
"version": 1,
"patch": {
"name": "string",
"enabled": true,
"boardId": "string",
"columnId": "string",
"auth": {
"mode": "hmac",
"externalSecret": "string",
"apiKey": "string",
"jwt": {
"issuer": "https://example.com",
"audience": "string",
"jwksUrl": "https://example.com"
}
},
"mapping": {
"severityMap": {},
"labels": [
"string"
],
"annotationAllowlist": [
"string"
],
"titleTemplate": "string",
"extract": {
"events": "string",
"fingerprint": [
"string"
],
"title": [
"string"
],
"severity": [
"string"
],
"status": [
"string"
],
"description": [
"string"
],
"occurredAt": [
"string"
],
"labels": "string",
"annotations": "string",
"links": [
"string"
],
"dedupKey": [
"string"
]
},
"statusMap": {},
"defaults": {
"title": "string",
"severity": "string",
"status": "firing"
}
},
"routing": {
"mode": "oncall",
"onCallAgent": "string",
"fallbackBroadcast": true,
"pdUserMap": {}
},
"lifecycle": "correlate",
"policy": {
"openPrForReview": true,
"autoReviewCycle": true,
"reviewerAgent": "string"
},
"limits": {
"ratePerMin": 1,
"payloadCapKb": 1
}
}
});result = await session.call_tool("update_integration", arguments={
"projectId": "string",
"integrationId": "string",
"version": 1,
"patch": {
"name": "string",
"enabled": true,
"boardId": "string",
"columnId": "string",
"auth": {
"mode": "hmac",
"externalSecret": "string",
"apiKey": "string",
"jwt": {
"issuer": "https://example.com",
"audience": "string",
"jwksUrl": "https://example.com"
}
},
"mapping": {
"severityMap": {},
"labels": [
"string"
],
"annotationAllowlist": [
"string"
],
"titleTemplate": "string",
"extract": {
"events": "string",
"fingerprint": [
"string"
],
"title": [
"string"
],
"severity": [
"string"
],
"status": [
"string"
],
"description": [
"string"
],
"occurredAt": [
"string"
],
"labels": "string",
"annotations": "string",
"links": [
"string"
],
"dedupKey": [
"string"
]
},
"statusMap": {},
"defaults": {
"title": "string",
"severity": "string",
"status": "firing"
}
},
"routing": {
"mode": "oncall",
"onCallAgent": "string",
"fallbackBroadcast": true,
"pdUserMap": {}
},
"lifecycle": "correlate",
"policy": {
"openPrForReview": true,
"autoReviewCycle": true,
"reviewerAgent": "string"
},
"limits": {
"ratePerMin": 1,
"payloadCapKb": 1
}
}
}){
"projectId": "string",
"integrationId": "string",
"version": 1,
"patch": {
"name": "string",
"enabled": true,
"boardId": "string",
"columnId": "string",
"auth": {
"mode": "hmac",
"externalSecret": "string",
"apiKey": "string",
"jwt": {
"issuer": "https://example.com",
"audience": "string",
"jwksUrl": "https://example.com"
}
},
"mapping": {
"severityMap": {},
"labels": [
"string"
],
"annotationAllowlist": [
"string"
],
"titleTemplate": "string",
"extract": {
"events": "string",
"fingerprint": [
"string"
],
"title": [
"string"
],
"severity": [
"string"
],
"status": [
"string"
],
"description": [
"string"
],
"occurredAt": [
"string"
],
"labels": "string",
"annotations": "string",
"links": [
"string"
],
"dedupKey": [
"string"
]
},
"statusMap": {},
"defaults": {
"title": "string",
"severity": "string",
"status": "firing"
}
},
"routing": {
"mode": "oncall",
"onCallAgent": "string",
"fallbackBroadcast": true,
"pdUserMap": {}
},
"lifecycle": "correlate",
"policy": {
"openPrForReview": true,
"autoReviewCycle": true,
"reviewerAgent": "string"
},
"limits": {
"ratePerMin": 1,
"payloadCapKb": 1
}
}
}{
"integration": {
"id": "string",
"projectId": "string",
"name": "string",
"source": "generic",
"enabled": true,
"target": {
"boardId": "string",
"columnId": "string"
},
"auth": {
"mode": "hmac",
"rotatedAt": "string",
"externallyIssued": true,
"dualAcceptUntil": "string"
},
"mapping": {},
"routing": {},
"lifecycle": "correlate",
"policy": {},
"limits": {
"ratePerMin": 0,
"payloadCapKb": 0
},
"version": 0,
"createdBy": "string",
"createdAt": "string",
"updatedAt": "string",
"endpointPath": "string",
"deletedAt": "string"
},
"secret": "string",
"shownOnce": true
}delete_integration
Delete an on-call integration. The endpoint stops answering immediately — a POST to it 404s, the same answer a wrong id gets, so a scanner still cannot tell which ids exist. THIS IS A TOMBSTONE, NOT AN ERASURE: the row is kept (hidden from list_integrations unless includeDeleted) so the delivery log for incidents this integration already filed stays readable, and those incident TICKETS are untouched — deleting a config does not rewrite history. Deliveries expire on their own retention TTL. Version-checked (STALE on mismatch); a second delete is NOT_FOUND. To pause a source instead, use update_integration { patch: { enabled: false } } — that is the reversible one.
Parameters
projectIdstringrequiredargumentintegrationIdstringrequiredargumentversioninteger[1, 9007199254740991]requiredargumentReturns
integrationobjectrequireddeletedbooleanrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "delete_integration",
"arguments": {
"projectId": "string",
"integrationId": "string",
"version": 1
}
}
}const result = await client.callTool("delete_integration", {
"projectId": "string",
"integrationId": "string",
"version": 1
});result = await session.call_tool("delete_integration", arguments={
"projectId": "string",
"integrationId": "string",
"version": 1
}){
"integration": {
"id": "string",
"projectId": "string",
"name": "string",
"source": "generic",
"enabled": true,
"target": {
"boardId": "string",
"columnId": "string"
},
"auth": {
"mode": "hmac",
"rotatedAt": "string",
"externallyIssued": true,
"dualAcceptUntil": "string"
},
"mapping": {},
"routing": {},
"lifecycle": "correlate",
"policy": {},
"limits": {
"ratePerMin": 0,
"payloadCapKb": 0
},
"version": 0,
"createdBy": "string",
"createdAt": "string",
"updatedAt": "string",
"endpointPath": "string",
"deletedAt": "string"
},
"deleted": true
}rotate_integration_secret
Rotate an hmac integration's webhook secret. The new secret is returned EXACTLY ONCE; the previous one keeps verifying for 24 hours so the source can be updated without dropping deliveries. Pass revokePrevious:true to end that window (the compromised-secret path) — the old secret stops verifying within ~60 SECONDS, not instantly: the ingest function caches decrypted verification material per instance for that long and the core has no way to push an invalidation. Plan a compromise response around that bound. Pass externalSecret when the source issues its own (PagerDuty).
Parameters
projectIdstringrequiredargumentintegrationIdstringrequiredargumentversioninteger[1, 9007199254740991]requiredargumentrevokePreviousbooleanargumentexternalSecretstringargumentReturns
integrationobjectrequiredsecretstringshownOnceboolean{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "rotate_integration_secret",
"arguments": {
"projectId": "string",
"integrationId": "string",
"version": 1,
"revokePrevious": true,
"externalSecret": "string"
}
}
}const result = await client.callTool("rotate_integration_secret", {
"projectId": "string",
"integrationId": "string",
"version": 1,
"revokePrevious": true,
"externalSecret": "string"
});result = await session.call_tool("rotate_integration_secret", arguments={
"projectId": "string",
"integrationId": "string",
"version": 1,
"revokePrevious": true,
"externalSecret": "string"
}){
"integration": {
"id": "string",
"projectId": "string",
"name": "string",
"source": "generic",
"enabled": true,
"target": {
"boardId": "string",
"columnId": "string"
},
"auth": {
"mode": "hmac",
"rotatedAt": "string",
"externallyIssued": true,
"dualAcceptUntil": "string"
},
"mapping": {},
"routing": {},
"lifecycle": "correlate",
"policy": {},
"limits": {
"ratePerMin": 0,
"payloadCapKb": 0
},
"version": 0,
"createdBy": "string",
"createdAt": "string",
"updatedAt": "string",
"endpointPath": "string",
"deletedAt": "string"
},
"secret": "string",
"shownOnce": true
}list_integrations
List the on-call webhook integrations configured on a project, newest first. Auth material is never returned — each row carries the auth MODE, when it last rotated and whether a dual-accept window is open, but no secret, key or hash. Deleted integrations are omitted; pass includeDeleted to see them (each carries deletedAt) when you need the name behind an old incident's delivery log.
Parameters
projectIdstringrequiredargumentlimitinteger[1, 200]argumentincludeDeletedbooleanargumentReturns
integrationsArray<object>required{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_integrations",
"arguments": {
"projectId": "string",
"limit": 1,
"includeDeleted": true
}
}
}const result = await client.callTool("list_integrations", {
"projectId": "string",
"limit": 1,
"includeDeleted": true
});result = await session.call_tool("list_integrations", arguments={
"projectId": "string",
"limit": 1,
"includeDeleted": true
}){
"integrations": [
{
"id": "string",
"projectId": "string",
"name": "string",
"source": "generic",
"enabled": true,
"target": {
"boardId": "string",
"columnId": "string"
},
"auth": {
"mode": "hmac",
"rotatedAt": "string",
"externallyIssued": true,
"dualAcceptUntil": "string"
},
"mapping": {},
"routing": {},
"lifecycle": "correlate",
"policy": {},
"limits": {
"ratePerMin": 0,
"payloadCapKb": 0
},
"version": 0,
"createdBy": "string",
"createdAt": "string",
"updatedAt": "string",
"endpointPath": "string",
"deletedAt": "string"
}
]
}list_deliveries
An integration's webhook delivery log, newest first: what arrived, what it did (created / appended / annotated / closed / coalesced / recorded / rejected / rate_limited), and which ticket it produced. Cursor-paged; nextCursor present means MORE ROWS EXIST, absent means the list is complete. Every delivery row carries the SAME retention window; it is the stored raw PAYLOAD of a refused delivery that expires sooner, and only the first 20 refusals per hour keep one at all.
Parameters
projectIdstringrequiredargumentintegrationIdstringrequiredargumentlimitinteger[1, 200]argumentcursorstringargumentkindstringfiringresolvedtestrejectedargumentReturns
deliveriesArray<object>requirednextCursorstring{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_deliveries",
"arguments": {
"projectId": "string",
"integrationId": "string",
"limit": 1,
"cursor": "string",
"kind": "firing"
}
}
}const result = await client.callTool("list_deliveries", {
"projectId": "string",
"integrationId": "string",
"limit": 1,
"cursor": "string",
"kind": "firing"
});result = await session.call_tool("list_deliveries", arguments={
"projectId": "string",
"integrationId": "string",
"limit": 1,
"cursor": "string",
"kind": "firing"
}){
"deliveries": [
{
"deliveryId": "string",
"ts": "string",
"kind": "firing",
"result": "string",
"taskId": "string",
"error": "string",
"fingerprint": "string",
"title": "string",
"severity": "string"
}
],
"nextCursor": "string"
}read_delivery_raw
The stored raw payload of a REJECTED delivery (a signature/auth failure), base64-encoded, for diagnosing a misconfigured sender. Returns null when the payload was never stored (over the hourly budget), has aged out of its short retention window, or the delivery was not a rejection. THESE ARE UNAUTHENTICATED ATTACKER-CONTROLLED BYTES: base64 so nothing renders them by accident, and any UI must show them as escaped text or serve them as a download, never inline.
Parameters
projectIdstringrequiredargumentintegrationIdstringrequiredargumentdeliveryIdstringrequiredargumentReturns
deliveryIdstringrequiredpayloadBase64string | nullrequiredcontentTypestringrequiredsuggestedFilenamestringrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "read_delivery_raw",
"arguments": {
"projectId": "string",
"integrationId": "string",
"deliveryId": "string"
}
}
}const result = await client.callTool("read_delivery_raw", {
"projectId": "string",
"integrationId": "string",
"deliveryId": "string"
});result = await session.call_tool("read_delivery_raw", arguments={
"projectId": "string",
"integrationId": "string",
"deliveryId": "string"
}){
"deliveryId": "string",
"payloadBase64": "string",
"contentType": "application/octet-stream",
"suggestedFilename": "string"
}replay_delivery
Re-process a stored delivery through the normal ingest path — correlation, lifecycle mode, routing and wake included. Replays the NORMALIZED event only: a delivery that failed signature verification has no normalized event and is refused, because replaying unverified bytes would be a second ingest door. Consumes the integration's rate budget like any other delivery.
Parameters
projectIdstringrequiredargumentintegrationIdstringrequiredargumentdeliveryIdstringrequiredargumentReturns
resultstringrequiredtaskIdstring | nullreplayedbooleanrequirednewDeliveryIdstringrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "replay_delivery",
"arguments": {
"projectId": "string",
"integrationId": "string",
"deliveryId": "string"
}
}
}const result = await client.callTool("replay_delivery", {
"projectId": "string",
"integrationId": "string",
"deliveryId": "string"
});result = await session.call_tool("replay_delivery", arguments={
"projectId": "string",
"integrationId": "string",
"deliveryId": "string"
}){
"result": "string",
"taskId": "string",
"replayed": true,
"newDeliveryId": "string"
}send_test_event
Inject a synthetic alert through the FULL ingest path — correlation, ticket creation, routing and the wake doorbell — to prove an integration is wired end to end. Creates a REAL ticket labelled test on the target board and consumes the integration's rate budget: that is part of what it proves. Signature verification is skipped because the event originates inside the core, never over the webhook endpoint.
Parameters
projectIdstringrequiredargumentintegrationIdstringrequiredargumenttitlestringargumentseveritystringargumentReturns
resultstringrequiredtaskIdstring | nulldeliveryIdstringrequireddoorbellstringrequired{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "send_test_event",
"arguments": {
"projectId": "string",
"integrationId": "string",
"title": "string",
"severity": "string"
}
}
}const result = await client.callTool("send_test_event", {
"projectId": "string",
"integrationId": "string",
"title": "string",
"severity": "string"
});result = await session.call_tool("send_test_event", arguments={
"projectId": "string",
"integrationId": "string",
"title": "string",
"severity": "string"
}){
"result": "string",
"taskId": "string",
"deliveryId": "string",
"doorbell": "string"
}Resources
board
Column layout + per-column task counts
Parameters
projectIdstringrequiredpathboardIdstringrequiredpathReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {
"uri": "batondeck://{projectId}/board/{boardId}"
}
}const result = await client.readResource("batondeck://{projectId}/board/{boardId}");result = await session.read_resource("batondeck://{projectId}/board/{boardId}"){
"content": [
{
"type": "text",
"text": "..."
}
]
}task
The task document
Parameters
projectIdstringrequiredpathtaskIdstringrequiredpathReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {
"uri": "batondeck://{projectId}/task/{taskId}"
}
}const result = await client.readResource("batondeck://{projectId}/task/{taskId}");result = await session.read_resource("batondeck://{projectId}/task/{taskId}"){
"content": [
{
"type": "text",
"text": "..."
}
]
}task-context
Composed briefing (summary, items, memory, deps)
Parameters
projectIdstringrequiredpathtaskIdstringrequiredpathReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {
"uri": "batondeck://{projectId}/task/{taskId}/context"
}
}const result = await client.readResource("batondeck://{projectId}/task/{taskId}/context");result = await session.read_resource("batondeck://{projectId}/task/{taskId}/context"){
"content": [
{
"type": "text",
"text": "..."
}
]
}board-feed
Recent events for a board
Parameters
projectIdstringrequiredpathboardIdstringrequiredpathReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {
"uri": "batondeck://{projectId}/board/{boardId}/feed"
}
}const result = await client.readResource("batondeck://{projectId}/board/{boardId}/feed");result = await session.read_resource("batondeck://{projectId}/board/{boardId}/feed"){
"content": [
{
"type": "text",
"text": "..."
}
]
}chronicle
Decision records and topic pages for this project. Paginated: when nextCursor is present more pages exist — re-read this URI with ?cursor=<nextCursor>.
Parameters
projectIdstringrequiredpathReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {
"uri": "batondeck://{projectId}/chronicle"
}
}const result = await client.readResource("batondeck://{projectId}/chronicle");result = await session.read_resource("batondeck://{projectId}/chronicle"){
"content": [
{
"type": "text",
"text": "..."
}
]
}chronicle-cursor
Decision records and topic pages for this project. Paginated: when nextCursor is present more pages exist — re-read this URI with ?cursor=<nextCursor>.
Parameters
projectIdstringrequiredpath?cursorstringrequiredpathReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {
"uri": "batondeck://{projectId}/chronicle{?cursor}"
}
}const result = await client.readResource("batondeck://{projectId}/chronicle{?cursor}");result = await session.read_resource("batondeck://{projectId}/chronicle{?cursor}"){
"content": [
{
"type": "text",
"text": "..."
}
]
}chronicle-page
A decision record with its current blocks and provenance
Parameters
projectIdstringrequiredpathpageIdstringrequiredpathReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {
"uri": "batondeck://{projectId}/chronicle/{pageId}"
}
}const result = await client.readResource("batondeck://{projectId}/chronicle/{pageId}");result = await session.read_resource("batondeck://{projectId}/chronicle/{pageId}"){
"content": [
{
"type": "text",
"text": "..."
}
]
}chronicle-history
Version history for a page, newest first. Paginated: when nextCursor is present older versions exist — re-read this URI with ?cursor=<nextCursor>.
Parameters
projectIdstringrequiredpathpageIdstringrequiredpathReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {
"uri": "batondeck://{projectId}/chronicle/{pageId}/versions"
}
}const result = await client.readResource("batondeck://{projectId}/chronicle/{pageId}/versions");result = await session.read_resource("batondeck://{projectId}/chronicle/{pageId}/versions"){
"content": [
{
"type": "text",
"text": "..."
}
]
}chronicle-history-cursor
Version history for a page, newest first. Paginated: when nextCursor is present older versions exist — re-read this URI with ?cursor=<nextCursor>.
Parameters
projectIdstringrequiredpathpageIdstringrequiredpath?cursorstringrequiredpathReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {
"uri": "batondeck://{projectId}/chronicle/{pageId}/versions{?cursor}"
}
}const result = await client.readResource("batondeck://{projectId}/chronicle/{pageId}/versions{?cursor}");result = await session.read_resource("batondeck://{projectId}/chronicle/{pageId}/versions{?cursor}"){
"content": [
{
"type": "text",
"text": "..."
}
]
}chronicle-version
One immutable version of a page, blocks included
Parameters
projectIdstringrequiredpathpageIdstringrequiredpathversionIdstringrequiredpathReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {
"uri": "batondeck://{projectId}/chronicle/{pageId}/versions/{versionId}"
}
}const result = await client.readResource("batondeck://{projectId}/chronicle/{pageId}/versions/{versionId}");result = await session.read_resource("batondeck://{projectId}/chronicle/{pageId}/versions/{versionId}"){
"content": [
{
"type": "text",
"text": "..."
}
]
}Prompts
pick_up_next_task
Claim and start the next eligible task on a board.
Parameters
projectIdstringrequiredargumentboardIdstringrequiredargumentReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "prompts/get",
"params": {
"name": "pick_up_next_task",
"arguments": {
"projectId": "<projectId>",
"boardId": "<boardId>"
}
}
}const result = await client.getPrompt("pick_up_next_task", {
projectId: "<projectId>",
boardId: "<boardId>",
});result = await session.get_prompt("pick_up_next_task", arguments={
"projectId": "<projectId>",
"boardId": "<boardId>",
}){
"content": [
{
"type": "text",
"text": "..."
}
]
}triage_inbox
Triage BACKLOG tasks: set priority/labels and move ready ones to READY.
Parameters
projectIdstringrequiredargumentboardIdstringrequiredargumentReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "prompts/get",
"params": {
"name": "triage_inbox",
"arguments": {
"projectId": "<projectId>",
"boardId": "<boardId>"
}
}
}const result = await client.getPrompt("triage_inbox", {
projectId: "<projectId>",
boardId: "<boardId>",
});result = await session.get_prompt("triage_inbox", arguments={
"projectId": "<projectId>",
"boardId": "<boardId>",
}){
"content": [
{
"type": "text",
"text": "..."
}
]
}summarize_for_handoff
Write a rolling summary capturing state before handing a task off.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "prompts/get",
"params": {
"name": "summarize_for_handoff",
"arguments": {
"projectId": "<projectId>",
"taskId": "<taskId>"
}
}
}const result = await client.getPrompt("summarize_for_handoff", {
projectId: "<projectId>",
taskId: "<taskId>",
});result = await session.get_prompt("summarize_for_handoff", arguments={
"projectId": "<projectId>",
"taskId": "<taskId>",
}){
"content": [
{
"type": "text",
"text": "..."
}
]
}decompose_into_subtasks
Break a task into actionable subtasks.
Parameters
projectIdstringrequiredargumenttaskIdstringrequiredargumentReturns
Returns MCP content array (text, image, or embedded resource).
{
"jsonrpc": "2.0",
"method": "prompts/get",
"params": {
"name": "decompose_into_subtasks",
"arguments": {
"projectId": "<projectId>",
"taskId": "<taskId>"
}
}
}const result = await client.getPrompt("decompose_into_subtasks", {
projectId: "<projectId>",
taskId: "<taskId>",
});result = await session.get_prompt("decompose_into_subtasks", arguments={
"projectId": "<projectId>",
"taskId": "<taskId>",
}){
"content": [
{
"type": "text",
"text": "..."
}
]
}