@kanvas/openclaw-plugin 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Kanvas OpenClaw Plugin
|
|
2
2
|
|
|
3
|
-
OpenClaw plugin that connects AI agents to [Kanvas](https://kanvas.dev)
|
|
3
|
+
OpenClaw plugin that connects AI agents to [Kanvas](https://kanvas.dev) your company's nervous system. Kanvas is the operational engine that connects all your data, tools, and workflows. For AI agents, it's what lets them actually run your business — not just talk about it.
|
|
4
4
|
|
|
5
5
|
This plugin gives agents direct access to CRM, inventory, orders, and messaging — 53 tools with auto-login, built-in system prompt context, and domain-specific skills.
|
|
6
6
|
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const PLAN_DETAIL_FIELDS = `
|
|
2
|
+
id
|
|
3
|
+
uuid
|
|
4
|
+
title
|
|
5
|
+
description
|
|
6
|
+
plan_type
|
|
7
|
+
status
|
|
8
|
+
priority
|
|
9
|
+
deadline_at
|
|
10
|
+
completion_pct
|
|
11
|
+
requires_human_approval
|
|
12
|
+
approved_at
|
|
13
|
+
review_outcome
|
|
14
|
+
started_at
|
|
15
|
+
completed_at
|
|
16
|
+
error_message
|
|
17
|
+
input
|
|
18
|
+
output
|
|
19
|
+
confidence_score
|
|
20
|
+
entity_namespace
|
|
21
|
+
entity_id
|
|
22
|
+
created_at
|
|
23
|
+
updated_at
|
|
24
|
+
agent { id name }
|
|
25
|
+
user { id firstname lastname }
|
|
26
|
+
approver { id firstname lastname }
|
|
27
|
+
parent { id title status }
|
|
28
|
+
tasks { id sequence title description status blocked_reason result started_at completed_at }
|
|
29
|
+
files { data { id uuid name url } }
|
|
30
|
+
tags { data { id name } }
|
|
31
|
+
`;
|
|
32
|
+
const PLAN_LIST_FIELDS = `
|
|
33
|
+
id
|
|
34
|
+
uuid
|
|
35
|
+
title
|
|
36
|
+
description
|
|
37
|
+
plan_type
|
|
38
|
+
status
|
|
39
|
+
priority
|
|
40
|
+
deadline_at
|
|
41
|
+
completion_pct
|
|
42
|
+
requires_human_approval
|
|
43
|
+
entity_namespace
|
|
44
|
+
entity_id
|
|
45
|
+
created_at
|
|
46
|
+
agent { id name }
|
|
47
|
+
tasks { id sequence title status blocked_reason }
|
|
48
|
+
`;
|
|
49
|
+
const TASK_FIELDS = `
|
|
50
|
+
id
|
|
51
|
+
uuid
|
|
52
|
+
sequence
|
|
53
|
+
title
|
|
54
|
+
description
|
|
55
|
+
status
|
|
56
|
+
blocked_reason
|
|
57
|
+
result
|
|
58
|
+
started_at
|
|
59
|
+
completed_at
|
|
60
|
+
created_at
|
|
61
|
+
updated_at
|
|
62
|
+
`;
|
|
63
|
+
export class NervousSystemService {
|
|
64
|
+
client;
|
|
65
|
+
constructor(client) {
|
|
66
|
+
this.client = client;
|
|
67
|
+
}
|
|
68
|
+
// ── Read ───────────────────────────────────────────────────
|
|
69
|
+
async listMyPlans(opts) {
|
|
70
|
+
const { agent_id, statuses, first = 50 } = opts;
|
|
71
|
+
const where = statuses && statuses.length
|
|
72
|
+
? {
|
|
73
|
+
AND: [
|
|
74
|
+
{ column: "AGENT_ID", operator: "EQ", value: agent_id },
|
|
75
|
+
{ column: "STATUS", operator: "IN", value: statuses },
|
|
76
|
+
],
|
|
77
|
+
}
|
|
78
|
+
: { column: "AGENT_ID", operator: "EQ", value: agent_id };
|
|
79
|
+
const query = `
|
|
80
|
+
query MyPlans($first: Int!, $where: QueryNervousSystemPlansWhereWhereConditions) {
|
|
81
|
+
nervousSystemPlans(
|
|
82
|
+
first: $first
|
|
83
|
+
where: $where
|
|
84
|
+
orderBy: [{ column: PRIORITY, order: DESC }, { column: DEADLINE_AT, order: ASC }]
|
|
85
|
+
) {
|
|
86
|
+
data { ${PLAN_LIST_FIELDS} }
|
|
87
|
+
paginatorInfo { currentPage lastPage total }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
return this.client.query(query, { first, where });
|
|
92
|
+
}
|
|
93
|
+
async listPlans(opts = {}) {
|
|
94
|
+
const { first = 25, where, orderBy } = opts;
|
|
95
|
+
const query = `
|
|
96
|
+
query ListPlans($first: Int!, $where: QueryNervousSystemPlansWhereWhereConditions, $orderBy: [QueryNervousSystemPlansOrderByOrderByClause!]) {
|
|
97
|
+
nervousSystemPlans(first: $first, where: $where, orderBy: $orderBy) {
|
|
98
|
+
data { ${PLAN_LIST_FIELDS} }
|
|
99
|
+
paginatorInfo { currentPage lastPage total }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
`;
|
|
103
|
+
return this.client.query(query, { first, where, orderBy });
|
|
104
|
+
}
|
|
105
|
+
async getPlan(id) {
|
|
106
|
+
const query = `
|
|
107
|
+
query GetPlan($id: ID!) {
|
|
108
|
+
nervousSystemPlan(id: $id) {
|
|
109
|
+
${PLAN_DETAIL_FIELDS}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
`;
|
|
113
|
+
return this.client.query(query, { id });
|
|
114
|
+
}
|
|
115
|
+
async getAgentCapabilities(agentId, framework) {
|
|
116
|
+
const query = `
|
|
117
|
+
query AgentCapabilities($agent_id: ID!, $framework: String) {
|
|
118
|
+
nervousSystemAgentCapabilities(agent_id: $agent_id, framework: $framework) {
|
|
119
|
+
skills {
|
|
120
|
+
id name description skill_type version is_active
|
|
121
|
+
}
|
|
122
|
+
tools {
|
|
123
|
+
id name description tool_type requires_permission version is_active
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
`;
|
|
128
|
+
return this.client.query(query, { agent_id: agentId, framework });
|
|
129
|
+
}
|
|
130
|
+
// ── Plan mutations ─────────────────────────────────────────
|
|
131
|
+
async createPlan(input) {
|
|
132
|
+
const mutation = `
|
|
133
|
+
mutation CreatePlan($input: CreateNervousSystemPlanInput!) {
|
|
134
|
+
createNervousSystemPlan(input: $input) {
|
|
135
|
+
${PLAN_DETAIL_FIELDS}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
`;
|
|
139
|
+
return this.client.query(mutation, { input });
|
|
140
|
+
}
|
|
141
|
+
async updatePlan(id, input) {
|
|
142
|
+
const mutation = `
|
|
143
|
+
mutation UpdatePlan($id: ID!, $input: UpdateNervousSystemPlanInput!) {
|
|
144
|
+
updateNervousSystemPlan(id: $id, input: $input) {
|
|
145
|
+
${PLAN_DETAIL_FIELDS}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
`;
|
|
149
|
+
return this.client.query(mutation, { id, input });
|
|
150
|
+
}
|
|
151
|
+
async approvePlan(id, input) {
|
|
152
|
+
const mutation = `
|
|
153
|
+
mutation ApprovePlan($id: ID!, $input: ApproveNervousSystemPlanInput!) {
|
|
154
|
+
approveNervousSystemPlan(id: $id, input: $input) {
|
|
155
|
+
id
|
|
156
|
+
status
|
|
157
|
+
requires_human_approval
|
|
158
|
+
approved_at
|
|
159
|
+
review_outcome
|
|
160
|
+
approver { id firstname lastname }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
`;
|
|
164
|
+
return this.client.query(mutation, { id, input });
|
|
165
|
+
}
|
|
166
|
+
async deletePlan(id) {
|
|
167
|
+
const mutation = `
|
|
168
|
+
mutation DeletePlan($id: ID!) {
|
|
169
|
+
deleteNervousSystemPlan(id: $id)
|
|
170
|
+
}
|
|
171
|
+
`;
|
|
172
|
+
return this.client.query(mutation, { id });
|
|
173
|
+
}
|
|
174
|
+
// ── Task mutations ─────────────────────────────────────────
|
|
175
|
+
async addTask(planId, input) {
|
|
176
|
+
const mutation = `
|
|
177
|
+
mutation AddTask($plan_id: ID!, $input: NervousSystemTaskInput!) {
|
|
178
|
+
addTaskToNervousSystemPlan(plan_id: $plan_id, input: $input) {
|
|
179
|
+
${TASK_FIELDS}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
`;
|
|
183
|
+
return this.client.query(mutation, { plan_id: planId, input });
|
|
184
|
+
}
|
|
185
|
+
async updateTaskStatus(id, input) {
|
|
186
|
+
const mutation = `
|
|
187
|
+
mutation UpdateTaskStatus($id: ID!, $input: UpdateNervousSystemTaskStatusInput!) {
|
|
188
|
+
updateNervousSystemTaskStatus(id: $id, input: $input) {
|
|
189
|
+
${TASK_FIELDS}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
return this.client.query(mutation, { id, input });
|
|
194
|
+
}
|
|
195
|
+
async deleteTask(id) {
|
|
196
|
+
const mutation = `
|
|
197
|
+
mutation DeleteTask($id: ID!) {
|
|
198
|
+
deleteNervousSystemTask(id: $id)
|
|
199
|
+
}
|
|
200
|
+
`;
|
|
201
|
+
return this.client.query(mutation, { id });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { SocialService } from "./domains/social/index.js";
|
|
|
7
7
|
import { EcosystemService } from "./domains/ecosystem/index.js";
|
|
8
8
|
import { DealsService } from "./domains/deals/index.js";
|
|
9
9
|
import { EventsService } from "./domains/events/index.js";
|
|
10
|
+
import { NervousSystemService } from "./domains/nervousSystem/index.js";
|
|
10
11
|
import { registerCrmTools } from "./tools/crm.js";
|
|
11
12
|
import { registerInventoryTools } from "./tools/inventory.js";
|
|
12
13
|
import { registerOrdersTools } from "./tools/orders.js";
|
|
@@ -14,6 +15,7 @@ import { registerSocialTools } from "./tools/social.js";
|
|
|
14
15
|
import { registerEcosystemTools } from "./tools/ecosystem.js";
|
|
15
16
|
import { registerDealsTools } from "./tools/deals.js";
|
|
16
17
|
import { registerEventsTools } from "./tools/events.js";
|
|
18
|
+
import { registerNervousSystemTools } from "./tools/nervousSystem.js";
|
|
17
19
|
import { toolResult } from "./tools/helpers.js";
|
|
18
20
|
const DEFAULT_API_URL = "https://graphapi.kanvas.dev/graphql";
|
|
19
21
|
// OpenClaw v2026.4.5+ calls register() per-agent-context (main, subagents,
|
|
@@ -29,6 +31,7 @@ let sharedSocial = null;
|
|
|
29
31
|
let sharedEcosystem = null;
|
|
30
32
|
let sharedDeals = null;
|
|
31
33
|
let sharedEvents = null;
|
|
34
|
+
let sharedNervousSystem = null;
|
|
32
35
|
let startupBannerShown = false;
|
|
33
36
|
let skipBannerShown = false;
|
|
34
37
|
function resolveConfig(pluginConfig) {
|
|
@@ -123,6 +126,7 @@ export default {
|
|
|
123
126
|
sharedEcosystem = new EcosystemService(sharedClient);
|
|
124
127
|
sharedDeals = new DealsService(sharedClient);
|
|
125
128
|
sharedEvents = new EventsService(sharedClient);
|
|
129
|
+
sharedNervousSystem = new NervousSystemService(sharedClient);
|
|
126
130
|
}
|
|
127
131
|
// Tools and hooks must be registered on every api object — each one is
|
|
128
132
|
// a separate agent context (main, subagents, cron lanes).
|
|
@@ -134,6 +138,7 @@ export default {
|
|
|
134
138
|
registerEcosystemTools(api, sharedEcosystem, ensureAuth);
|
|
135
139
|
registerDealsTools(api, sharedDeals, ensureAuth);
|
|
136
140
|
registerEventsTools(api, sharedEvents, ensureAuth);
|
|
141
|
+
registerNervousSystemTools(api, sharedNervousSystem, ensureAuth);
|
|
137
142
|
api.registerTool({
|
|
138
143
|
name: "kanvas_test_connection",
|
|
139
144
|
label: "Test Connection",
|
|
@@ -158,7 +163,7 @@ export default {
|
|
|
158
163
|
}, { commands: ["setup"] });
|
|
159
164
|
if (!startupBannerShown) {
|
|
160
165
|
startupBannerShown = true;
|
|
161
|
-
api.logger.info("Kanvas plugin registered —
|
|
166
|
+
api.logger.info("Kanvas plugin registered — 84 tools loaded");
|
|
162
167
|
}
|
|
163
168
|
},
|
|
164
169
|
};
|
|
@@ -279,6 +284,21 @@ Use when the user asks about companies, branches/locations, user management, rol
|
|
|
279
284
|
- \`kanvas_assign_role_to_user\` → assign roles (e.g. super admin). Call \`kanvas_list_roles\` first to get role IDs.
|
|
280
285
|
- \`kanvas_remove_role_from_user\` → remove a role from a user
|
|
281
286
|
|
|
287
|
+
**Nervous System (Plans, Tasks, Activities)**
|
|
288
|
+
This is how humans assign work to you. Read the \`nervous-system-working\` skill for the full protocol. Treat every non-trivial request as a Plan; plan first, execute second. Move statuses deliberately — humans watch the kanban.
|
|
289
|
+
- \`kanvas_list_my_plans\` → find work assigned to you (filter by statuses: draft/awaiting_approval/active/blocked)
|
|
290
|
+
- \`kanvas_get_plan\` → full plan detail (description, input, tasks, files, parent)
|
|
291
|
+
- \`kanvas_create_plan\` → create plan or sub-plan, optionally with seeded tasks
|
|
292
|
+
- \`kanvas_update_plan\` → transition status (draft→active→done/blocked/failed), set output JSON, attach files
|
|
293
|
+
- \`kanvas_approve_plan\` → approve/reject a plan in awaiting_approval
|
|
294
|
+
- \`kanvas_add_task\` → append a task to the checklist (sequence determines order)
|
|
295
|
+
- \`kanvas_update_task_status\` → pending → in_progress → done (or blocked/failed)
|
|
296
|
+
- \`kanvas_list_agent_capabilities\` → list skills/tools granted to an agent
|
|
297
|
+
- \`kanvas_delete_plan\` / \`kanvas_delete_task\` → delete
|
|
298
|
+
- For Activities (the conversation channel), use \`kanvas_create_message\` with \`channel_slug = plan.uuid\` and \`message_verb: "comment"\`. Read with \`kanvas_list_channel_messages\` using the same slug.
|
|
299
|
+
- **Always re-read the channel** before acting on a plan you've worked on before.
|
|
300
|
+
- **Never act** on a plan with \`requires_human_approval=true\` until it's flipped to active.
|
|
301
|
+
|
|
282
302
|
**Diagnostics**
|
|
283
303
|
- \`kanvas_test_connection\` → verify the API is reachable
|
|
284
304
|
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { toolResult } from "./helpers.js";
|
|
3
|
+
const TaskInputSchema = Type.Object({
|
|
4
|
+
title: Type.String({ description: "Task title (verb phrase, e.g. 'Crawl /features')" }),
|
|
5
|
+
sequence: Type.Optional(Type.Number({ description: "Execution order (0, 1, 2...)" })),
|
|
6
|
+
description: Type.Optional(Type.String()),
|
|
7
|
+
status: Type.Optional(Type.String({ description: "pending | in_progress | blocked | done | failed | cancelled" })),
|
|
8
|
+
result: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
9
|
+
blocked_reason: Type.Optional(Type.String()),
|
|
10
|
+
});
|
|
11
|
+
const FilesInputSchema = Type.Array(Type.Object({
|
|
12
|
+
url: Type.String({ description: "Public URL of the file" }),
|
|
13
|
+
name: Type.String({ description: "Display name" }),
|
|
14
|
+
field_name: Type.Optional(Type.String()),
|
|
15
|
+
}), { description: "Files to attach (appended, not replaced)" });
|
|
16
|
+
const PlanStatusSchema = Type.String({
|
|
17
|
+
description: "Plan status: draft | awaiting_approval | active | blocked | done | failed | cancelled",
|
|
18
|
+
});
|
|
19
|
+
const TaskStatusSchema = Type.String({
|
|
20
|
+
description: "Task status: pending | in_progress | blocked | done | failed | cancelled",
|
|
21
|
+
});
|
|
22
|
+
export function registerNervousSystemTools(api, service, ensureAuth) {
|
|
23
|
+
// ── Read ───────────────────────────────────────────────────
|
|
24
|
+
api.registerTool({
|
|
25
|
+
name: "kanvas_list_my_plans",
|
|
26
|
+
label: "List My Plans",
|
|
27
|
+
description: "List nervous-system plans assigned to an agent. Filter by statuses to see open work " +
|
|
28
|
+
"(e.g. ['draft','awaiting_approval','active','blocked']). Returns plans ordered by priority desc, deadline asc.",
|
|
29
|
+
parameters: Type.Object({
|
|
30
|
+
agent_id: Type.Union([Type.String(), Type.Number()], { description: "The agent's ID" }),
|
|
31
|
+
statuses: Type.Optional(Type.Array(Type.String(), {
|
|
32
|
+
description: "Statuses to include. Default: all.",
|
|
33
|
+
})),
|
|
34
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
35
|
+
}),
|
|
36
|
+
async execute(_id, params) {
|
|
37
|
+
await ensureAuth();
|
|
38
|
+
return toolResult(await service.listMyPlans(params));
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
api.registerTool({
|
|
42
|
+
name: "kanvas_list_plans",
|
|
43
|
+
label: "List Plans (admin)",
|
|
44
|
+
description: "List nervous-system plans with arbitrary where filters. Allowed where columns: " +
|
|
45
|
+
"ID, UUID, AGENT_ID, USERS_ID, ENTITY_NAMESPACE, ENTITY_ID, PLAN_TYPE, STATUS, PRIORITY, PARENT_PLAN_ID.",
|
|
46
|
+
parameters: Type.Object({
|
|
47
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 25)" })),
|
|
48
|
+
where: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
49
|
+
orderBy: Type.Optional(Type.Array(Type.Object({
|
|
50
|
+
column: Type.String(),
|
|
51
|
+
order: Type.String({ description: "ASC or DESC" }),
|
|
52
|
+
}))),
|
|
53
|
+
}),
|
|
54
|
+
async execute(_id, params) {
|
|
55
|
+
await ensureAuth();
|
|
56
|
+
return toolResult(await service.listPlans(params));
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
api.registerTool({
|
|
60
|
+
name: "kanvas_get_plan",
|
|
61
|
+
label: "Get Plan",
|
|
62
|
+
description: "Get full plan detail: description, input/output, tasks, files, tags, agent, user, parent, completion %.",
|
|
63
|
+
parameters: Type.Object({
|
|
64
|
+
id: Type.String({ description: "Plan ID" }),
|
|
65
|
+
}),
|
|
66
|
+
async execute(_id, params) {
|
|
67
|
+
await ensureAuth();
|
|
68
|
+
return toolResult(await service.getPlan(params.id));
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
api.registerTool({
|
|
72
|
+
name: "kanvas_list_agent_capabilities",
|
|
73
|
+
label: "List Agent Capabilities",
|
|
74
|
+
description: "List the skills and tools an agent has been granted. Use this to verify you can do something before agreeing to a plan.",
|
|
75
|
+
parameters: Type.Object({
|
|
76
|
+
agent_id: Type.String({ description: "Agent ID" }),
|
|
77
|
+
framework: Type.Optional(Type.String({ description: "Framework filter (optional)" })),
|
|
78
|
+
}),
|
|
79
|
+
async execute(_id, params) {
|
|
80
|
+
await ensureAuth();
|
|
81
|
+
return toolResult(await service.getAgentCapabilities(params.agent_id, params.framework));
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
// ── Plan write ─────────────────────────────────────────────
|
|
85
|
+
api.registerTool({
|
|
86
|
+
name: "kanvas_create_plan",
|
|
87
|
+
label: "Create Plan",
|
|
88
|
+
description: "Create a nervous-system plan (or sub-plan when parent_plan_id is provided). " +
|
|
89
|
+
"Include a tasks array to seed the checklist on creation. For sensitive work, set requires_human_approval=true " +
|
|
90
|
+
"to gate the plan in awaiting_approval status until a human approves.",
|
|
91
|
+
parameters: Type.Object({
|
|
92
|
+
title: Type.String(),
|
|
93
|
+
plan_type: Type.String({ description: 'e.g. "data_migration", "outreach", "report_generation"' }),
|
|
94
|
+
description: Type.Optional(Type.String()),
|
|
95
|
+
agent_id: Type.Optional(Type.Number()),
|
|
96
|
+
users_id: Type.Optional(Type.Number()),
|
|
97
|
+
parent_plan_id: Type.Optional(Type.Number()),
|
|
98
|
+
entity_namespace: Type.Optional(Type.String({ description: "FQCN of the related entity (e.g. App\\\\Models\\\\Lead)" })),
|
|
99
|
+
entity_id: Type.Optional(Type.Number()),
|
|
100
|
+
status: Type.Optional(PlanStatusSchema),
|
|
101
|
+
priority: Type.Optional(Type.Number({ description: "Higher = more important" })),
|
|
102
|
+
deadline_at: Type.Optional(Type.String({ description: "ISO datetime" })),
|
|
103
|
+
input: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
104
|
+
output: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
105
|
+
confidence_score: Type.Optional(Type.Number()),
|
|
106
|
+
requires_human_approval: Type.Optional(Type.Boolean()),
|
|
107
|
+
tasks: Type.Optional(Type.Array(TaskInputSchema)),
|
|
108
|
+
files: Type.Optional(FilesInputSchema),
|
|
109
|
+
}),
|
|
110
|
+
async execute(_id, params) {
|
|
111
|
+
await ensureAuth();
|
|
112
|
+
return toolResult(await service.createPlan(params));
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
api.registerTool({
|
|
116
|
+
name: "kanvas_update_plan",
|
|
117
|
+
label: "Update Plan",
|
|
118
|
+
description: "Update a plan's status, title, description, output, or attach files. " +
|
|
119
|
+
"Use status transitions to signal progress: draft→active to start, active→blocked when stuck, active→done/failed when finished.",
|
|
120
|
+
parameters: Type.Object({
|
|
121
|
+
id: Type.String({ description: "Plan ID" }),
|
|
122
|
+
title: Type.Optional(Type.String()),
|
|
123
|
+
description: Type.Optional(Type.String()),
|
|
124
|
+
status: Type.Optional(PlanStatusSchema),
|
|
125
|
+
priority: Type.Optional(Type.Number()),
|
|
126
|
+
deadline_at: Type.Optional(Type.String()),
|
|
127
|
+
input: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
128
|
+
output: Type.Optional(Type.Record(Type.String(), Type.Unknown()), {
|
|
129
|
+
description: "Final output JSON (set this before flipping to done)",
|
|
130
|
+
}),
|
|
131
|
+
confidence_score: Type.Optional(Type.Number()),
|
|
132
|
+
requires_human_approval: Type.Optional(Type.Boolean()),
|
|
133
|
+
files: Type.Optional(FilesInputSchema),
|
|
134
|
+
}),
|
|
135
|
+
async execute(_id, params) {
|
|
136
|
+
await ensureAuth();
|
|
137
|
+
const { id, ...input } = params;
|
|
138
|
+
return toolResult(await service.updatePlan(id, input));
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
api.registerTool({
|
|
142
|
+
name: "kanvas_approve_plan",
|
|
143
|
+
label: "Approve Plan",
|
|
144
|
+
description: "Approve or reject a plan that is in awaiting_approval status. Approving flips it to active; rejecting flips it to cancelled.",
|
|
145
|
+
parameters: Type.Object({
|
|
146
|
+
id: Type.String({ description: "Plan ID" }),
|
|
147
|
+
approved: Type.Boolean({ description: "true to approve, false to reject" }),
|
|
148
|
+
review_outcome: Type.Optional(Type.String({ description: "Optional reviewer note" })),
|
|
149
|
+
}),
|
|
150
|
+
async execute(_id, params) {
|
|
151
|
+
await ensureAuth();
|
|
152
|
+
return toolResult(await service.approvePlan(params.id, {
|
|
153
|
+
approved: params.approved,
|
|
154
|
+
review_outcome: params.review_outcome,
|
|
155
|
+
}));
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
api.registerTool({
|
|
159
|
+
name: "kanvas_delete_plan",
|
|
160
|
+
label: "Delete Plan",
|
|
161
|
+
description: "Delete a plan by ID.",
|
|
162
|
+
parameters: Type.Object({
|
|
163
|
+
id: Type.String({ description: "Plan ID" }),
|
|
164
|
+
}),
|
|
165
|
+
async execute(_id, params) {
|
|
166
|
+
await ensureAuth();
|
|
167
|
+
return toolResult(await service.deletePlan(params.id));
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
// ── Task write ─────────────────────────────────────────────
|
|
171
|
+
api.registerTool({
|
|
172
|
+
name: "kanvas_add_task",
|
|
173
|
+
label: "Add Task to Plan",
|
|
174
|
+
description: "Append a task to a plan's checklist. Sequence determines execution order (0, 1, 2...).",
|
|
175
|
+
parameters: Type.Object({
|
|
176
|
+
plan_id: Type.String({ description: "Plan ID" }),
|
|
177
|
+
title: Type.String({ description: "Task title (verb phrase)" }),
|
|
178
|
+
sequence: Type.Optional(Type.Number()),
|
|
179
|
+
description: Type.Optional(Type.String()),
|
|
180
|
+
status: Type.Optional(TaskStatusSchema),
|
|
181
|
+
}),
|
|
182
|
+
async execute(_id, params) {
|
|
183
|
+
await ensureAuth();
|
|
184
|
+
const { plan_id, ...input } = params;
|
|
185
|
+
return toolResult(await service.addTask(plan_id, input));
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
api.registerTool({
|
|
189
|
+
name: "kanvas_update_task_status",
|
|
190
|
+
label: "Update Task Status",
|
|
191
|
+
description: "Move a task through its lifecycle: pending → in_progress → done (or blocked/failed/cancelled). " +
|
|
192
|
+
"Set 'result' as JSON when done; set 'blocked_reason' when blocked.",
|
|
193
|
+
parameters: Type.Object({
|
|
194
|
+
id: Type.String({ description: "Task ID" }),
|
|
195
|
+
status: TaskStatusSchema,
|
|
196
|
+
result: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
197
|
+
blocked_reason: Type.Optional(Type.String()),
|
|
198
|
+
}),
|
|
199
|
+
async execute(_id, params) {
|
|
200
|
+
await ensureAuth();
|
|
201
|
+
const { id, ...input } = params;
|
|
202
|
+
return toolResult(await service.updateTaskStatus(id, input));
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
api.registerTool({
|
|
206
|
+
name: "kanvas_delete_task",
|
|
207
|
+
label: "Delete Task",
|
|
208
|
+
description: "Delete a task by ID.",
|
|
209
|
+
parameters: Type.Object({
|
|
210
|
+
id: Type.String({ description: "Task ID" }),
|
|
211
|
+
}),
|
|
212
|
+
async execute(_id, params) {
|
|
213
|
+
await ensureAuth();
|
|
214
|
+
return toolResult(await service.deleteTask(params.id));
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nervous-system-working
|
|
3
|
+
description: How an agent finds work assigned to it, plans the work as a checklist, executes it task by task, asks humans for approval when required, and leaves comments to communicate. Use this skill any time the agent is asked to "do" something for a user or company — the work belongs in a Plan, the steps belong in Tasks, and the conversation belongs in the Activities channel. Triggers on phrases like "find my work", "what do I have to do", "I need to plan this", "can you do X", "block until human approves", "leave a note for the user", "I'm stuck and need help".
|
|
4
|
+
metadata: {"openclaw":{"requires":{"config":["plugins.entries.kanvas"]}}}
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Working in the Nervous System (Plans, Tasks, Activities)
|
|
8
|
+
|
|
9
|
+
You're an agent inside Kanvas. Humans and other systems assign work to you through a structured object called a **Plan**. A Plan is your "issue ticket" — what you're being asked to do, broken down into Tasks (a checklist), with files attached, tags applied, and an Activities channel where you talk to the humans involved.
|
|
10
|
+
|
|
11
|
+
Treat every non-trivial request as a Plan: **plan first, execute second**. When in doubt, *write the checklist down* (as Tasks) before doing the work. This is how humans see what you're doing and trust you.
|
|
12
|
+
|
|
13
|
+
## The shape of your work
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Plan ─┬─ Tasks (the checklist of steps)
|
|
17
|
+
├─ Activities channel (where you talk to humans)
|
|
18
|
+
├─ Files (mockups, screenshots, attachments)
|
|
19
|
+
├─ Tags (urgent, design-review, etc.)
|
|
20
|
+
└─ Status (draft → active → done | blocked | failed | cancelled)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The status determines which kanban column the human sees you in. Move it deliberately.
|
|
24
|
+
|
|
25
|
+
| Status | Meaning | When to be in it |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `draft` | You're still figuring out the plan / haven't started | Right after creation, before you've written tasks |
|
|
28
|
+
| `awaiting_approval` | The plan needs a human review before you act | Set automatically when `requires_human_approval=true` |
|
|
29
|
+
| `active` | You're working on it right now | After you've claimed it and started executing |
|
|
30
|
+
| `blocked` | You can't proceed without input | Stuck on a question, missing data, external dependency |
|
|
31
|
+
| `done` | All tasks completed successfully | Final, terminal — celebrate |
|
|
32
|
+
| `failed` | You tried, you can't finish, the human needs to know | Final, terminal — leave a comment explaining what broke |
|
|
33
|
+
| `cancelled` | Someone (human or you) decided this shouldn't happen | Final, terminal |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1. Find work assigned to you
|
|
38
|
+
|
|
39
|
+
You have an `agent_id`. Use `kanvas_list_my_plans` to list everything you've been asked to do, filtered to the open statuses:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
kanvas_list_my_plans({ agent_id: <me>, statuses: ["draft", "awaiting_approval", "active", "blocked"] })
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Heuristics for picking what to do next:**
|
|
46
|
+
1. Anything with `status='blocked'` and a recent comment from a human → unblock it first. They were waiting on you.
|
|
47
|
+
2. Then anything `awaiting_approval` that the user just approved (re-list, you'll find it `active` now) → start executing.
|
|
48
|
+
3. Then `active` plans you've already started — finish them, don't fan out.
|
|
49
|
+
4. Only after that, pick up new `draft` work. Higher `priority` first; closer `deadline_at` next.
|
|
50
|
+
|
|
51
|
+
Do **not** start a plan whose `requires_human_approval = true` and `status = awaiting_approval`. Wait for a human to flip it to `active` via the approve mutation.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 2. Read the plan thoroughly before acting
|
|
56
|
+
|
|
57
|
+
Before you do anything, fetch full detail with `kanvas_get_plan({ id })` and **read everything**.
|
|
58
|
+
|
|
59
|
+
The fields that matter most for your decisions:
|
|
60
|
+
- **`description`** — the human's brief. Usually free-text. Read it.
|
|
61
|
+
- **`input`** — structured payload (JSON). Often contains parameters, IDs, prior context. **Always check it.**
|
|
62
|
+
- **`tasks`** — if a human pre-loaded a checklist, follow it. If empty, you should create one (see §3).
|
|
63
|
+
- **`entity_namespace` + `entity_id`** — the plan is *about* a specific entity (e.g. a Lead, a Deal, a Project). Fetch that entity's detail too.
|
|
64
|
+
- **`files`** — mockups, screenshots, references. Don't ignore them.
|
|
65
|
+
- **`parent`** — if non-null, this is a sub-plan. Look at the parent for upstream context.
|
|
66
|
+
- **`tags`** — surface hints from the human (`urgent`, `legal-review-needed`, etc.).
|
|
67
|
+
|
|
68
|
+
If you don't understand the brief after reading all of this, **don't guess**. Leave a comment on the Activities channel asking for clarification and transition to `blocked` (see §6).
|
|
69
|
+
|
|
70
|
+
Also re-read the plan's Activities messages with `kanvas_list_channel_messages({ channel_slug: <plan.uuid>, first: 20 })`. **Read the latest messages every time you re-enter a plan.** A human may have answered a question, redirected the work, or cancelled the plan since your last action.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 3. Plan first — write the tasks down
|
|
75
|
+
|
|
76
|
+
If the plan has no tasks (or only one vague task), break it into a real checklist before executing. **The checklist is the contract** — the human sees it on the kanban card and watches you work through it.
|
|
77
|
+
|
|
78
|
+
Two ways to add tasks:
|
|
79
|
+
|
|
80
|
+
- **At plan creation** (`kanvas_create_plan` with a `tasks: [...]` array) — recommended.
|
|
81
|
+
- **On an existing plan**: `kanvas_add_task({ plan_id, title, sequence, status: "pending" })`
|
|
82
|
+
|
|
83
|
+
**Sequence matters** — lower `sequence` runs first. Use 0, 1, 2… in execution order.
|
|
84
|
+
|
|
85
|
+
**Granularity:** tasks should be 5–60 minutes of work each. Too coarse and humans can't see progress; too fine and the kanban gets noisy. If in doubt, prefer fewer + slightly bigger tasks — you can always add more later.
|
|
86
|
+
|
|
87
|
+
**Names should be verbs**: "Crawl /features", "Edit demo video", "Email Maria about objection". Not "Step 1" or "Crawling".
|
|
88
|
+
|
|
89
|
+
Once tasks are in place, transition the plan to `active`:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
kanvas_update_plan({ id, status: "active" })
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This stamps `started_at` on the backend automatically — you don't pass it.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 4. Execute task by task
|
|
100
|
+
|
|
101
|
+
For each task, in sequence order:
|
|
102
|
+
|
|
103
|
+
1. **Mark it `in_progress`:**
|
|
104
|
+
```
|
|
105
|
+
kanvas_update_task_status({ id, status: "in_progress" })
|
|
106
|
+
```
|
|
107
|
+
Only one task should be `in_progress` at a time.
|
|
108
|
+
|
|
109
|
+
2. **Do the work.** Use whatever tools you have. Output should be captured in `result` if it's structured (an artifact reference, a JSON record, etc.).
|
|
110
|
+
|
|
111
|
+
3. **Mark it `done`:**
|
|
112
|
+
```
|
|
113
|
+
kanvas_update_task_status({ id, status: "done", result: { ... } })
|
|
114
|
+
```
|
|
115
|
+
The plan's `completion_pct` recomputes automatically.
|
|
116
|
+
|
|
117
|
+
4. **Loop** to the next pending task.
|
|
118
|
+
|
|
119
|
+
When you transition a task, the system emits a ledger event AND a Pusher broadcast — humans watching the kanban see your progress in real time. **You don't need to do anything extra to "notify" them**; status transitions are the notification.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 5. Add files / artifacts as you produce them
|
|
124
|
+
|
|
125
|
+
If a task produces an artifact (a generated image, a CSV, a report), attach it to the plan:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
kanvas_update_plan({ id, files: [{ url: "...", name: "..." }] })
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Files are appended (not replaced). Humans see them on the ticket detail's right-hand panel and in any UI rendering `plan.files`.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 6. Talk to humans through the Activities channel
|
|
136
|
+
|
|
137
|
+
Every plan has a Social Channel named `"Activities"` with `slug = plan.uuid`. This is your conversation thread with the humans on the ticket. Use it to:
|
|
138
|
+
|
|
139
|
+
- **Report progress on something interesting** ("I found 14 broken links — 3 in /features, 11 in /docs. Continuing.")
|
|
140
|
+
- **Ask a question when stuck** ("The brief says 'short demo' — should I cap at 60s or 90s?")
|
|
141
|
+
- **Surface a finding worth a human glance** ("The Lead's email bounced — flagged it on the lead record. Should I keep going with SMS or pause?")
|
|
142
|
+
- **Announce blockers** *before* you flip to `blocked` so humans can pre-empt the wait
|
|
143
|
+
|
|
144
|
+
**Posting a comment** uses the existing message tool with the plan's UUID as the channel slug:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
kanvas_create_message({
|
|
148
|
+
message_verb: "comment",
|
|
149
|
+
message: { text: "..." },
|
|
150
|
+
channel_slug: <plan.uuid>,
|
|
151
|
+
is_public: 1
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Reading messages**:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
kanvas_list_channel_messages({ channel_slug: <plan.uuid>, first: 50 })
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Tone for comments
|
|
162
|
+
|
|
163
|
+
- Concise. One paragraph max.
|
|
164
|
+
- State the observation, then the implication, then the proposed next step.
|
|
165
|
+
- Don't apologize, don't pad. "Email bounced. Pausing outreach. Open: try SMS, or close as bad-contact?" beats "Hi! I noticed that unfortunately the email seems to have bounced..."
|
|
166
|
+
- If you're asking a question, put it on the last line, single sentence.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 7. Approval gates — when humans must say yes first
|
|
171
|
+
|
|
172
|
+
If a plan has `requires_human_approval = true`, the system creates it with `status = awaiting_approval`. You must **not** execute its tasks until a human flips it to `active`.
|
|
173
|
+
|
|
174
|
+
You can:
|
|
175
|
+
- Read the plan to understand what's being requested
|
|
176
|
+
- Add tasks to flesh out your proposed approach (this signals to the human what you intend to do)
|
|
177
|
+
- Leave a comment on the Activities channel explaining your plan and asking for approval
|
|
178
|
+
|
|
179
|
+
You **cannot**:
|
|
180
|
+
- Move tasks to `in_progress`
|
|
181
|
+
- Move the plan to `active` yourself
|
|
182
|
+
- Take any side-effecting action
|
|
183
|
+
|
|
184
|
+
When the human approves (via `kanvas_approve_plan`), the plan flips to `active`. If they deny, it flips to `cancelled`.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 8. When stuck — block the plan, don't fail it
|
|
189
|
+
|
|
190
|
+
You'll hit cases where you can't proceed without input. **Block the plan, don't fail it.** Failing is for "I tried and broke things"; blocking is for "I'm waiting on something."
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
kanvas_update_task_status({ id, status: "blocked", blocked_reason: "..." })
|
|
194
|
+
kanvas_update_plan({ id, status: "blocked" })
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Always pair the block with a comment** on the Activities channel explaining what you need. The blocked status alone is opaque; the comment is what unblocks you.
|
|
198
|
+
|
|
199
|
+
Bad: silent block. The human sees red on the kanban with no context.
|
|
200
|
+
Good: block + comment ("Blocked — need the API key for SendGrid. The one in the input expired yesterday.").
|
|
201
|
+
|
|
202
|
+
When the human resolves the block, they'll either reply on the channel or update the plan/task status. **Re-read the channel before acting.**
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 9. Sub-plans — when a task is too big
|
|
207
|
+
|
|
208
|
+
If during execution a task turns out to be a substantial sub-project (5+ steps of its own), don't try to do it inline. Create a **child plan**:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
kanvas_create_plan({
|
|
212
|
+
title: "Migrate the 8000 legacy contacts",
|
|
213
|
+
plan_type: "data_migration",
|
|
214
|
+
parent_plan_id: 127,
|
|
215
|
+
agent_id: <me>,
|
|
216
|
+
status: "draft",
|
|
217
|
+
tasks: [
|
|
218
|
+
{ title: "Profile the source schema", sequence: 0 },
|
|
219
|
+
{ title: "Map fields to target schema", sequence: 1 }
|
|
220
|
+
]
|
|
221
|
+
})
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The parent task that spawned this can transition to `done` immediately (your work is to start the sub-plan, not finish the migration personally), or to `in_progress` (waiting on the sub-plan to finish). Use whichever matches the human's expectation — when in doubt, leave a comment.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 10. Finishing — done vs. failed vs. cancelled
|
|
229
|
+
|
|
230
|
+
When all tasks are `done`:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
kanvas_update_plan({
|
|
234
|
+
id,
|
|
235
|
+
status: "done",
|
|
236
|
+
output: {
|
|
237
|
+
summary: "...",
|
|
238
|
+
artifacts: [...],
|
|
239
|
+
metrics: { ... }
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Set a useful `output` — a JSON summary of what was produced. Humans use this without re-reading the whole task list.
|
|
245
|
+
|
|
246
|
+
If something genuinely broke and a human needs to take over:
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
kanvas_update_plan({
|
|
250
|
+
id,
|
|
251
|
+
status: "failed",
|
|
252
|
+
output: { summary: "Couldn't proceed. Reason and recovery suggestion in the Activities channel." }
|
|
253
|
+
})
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**Always pair `failed` with a comment** explaining what broke and what you tried. "It didn't work" is not an acceptable terminal state.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 11. Pre-emptive comments — don't wait to be asked
|
|
261
|
+
|
|
262
|
+
When something *interesting* happens — even if not blocking — a short comment is almost always worth it. The human's mental model of you depends on these comments:
|
|
263
|
+
|
|
264
|
+
- "Found that the lead's CRM record disagrees with the form submission on `phone`. Going with the form value since it's more recent."
|
|
265
|
+
- "Switched from `gpt-4` to `claude-sonnet-4-7` mid-run because the JSON output kept malforming. Re-running task 3."
|
|
266
|
+
- "The deadline is in 4 hours, this will likely take 6. Want to deprioritize task 5 (nice-to-have) to fit?"
|
|
267
|
+
|
|
268
|
+
Three rules of thumb:
|
|
269
|
+
1. If you made a non-obvious decision the human couldn't have predicted from the brief → comment.
|
|
270
|
+
2. If you're going to run for more than ~10 minutes without a status change → comment progress.
|
|
271
|
+
3. If your confidence on the outcome is < 70% → comment what your confidence is and why.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 12. Meta — when to refuse
|
|
276
|
+
|
|
277
|
+
Some things you should not do, even if assigned:
|
|
278
|
+
|
|
279
|
+
- **Destructive actions on data you don't own** — if the plan asks you to "delete all leads from before 2024" and you have no signed-off retention policy, *block + comment*. Don't ship destruction silently.
|
|
280
|
+
- **Workflows that move money / send to many recipients without approval gating** — even if `requires_human_approval=false`, if the action's blast radius is high, switch the plan to `awaiting_approval` yourself by setting `requires_human_approval=true` and `status=awaiting_approval`, and leave a comment explaining why.
|
|
281
|
+
- **Anything outside your declared skills/tools** — your `kanvas_list_agent_capabilities` tool tells you what you're allowed to do. If a task requires something not in your grants, *block + comment* asking for the grant.
|
|
282
|
+
|
|
283
|
+
Better to ask once than apologize a hundred times.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## 13. Summary checklist for every plan you touch
|
|
288
|
+
|
|
289
|
+
- [ ] Read the full plan including `input`, `description`, attached files, and the **last 10 messages** on the Activities channel (`kanvas_list_channel_messages`)
|
|
290
|
+
- [ ] Verify your skills/tools cover what's needed (`kanvas_list_agent_capabilities`)
|
|
291
|
+
- [ ] If no tasks, write the checklist before doing anything (`kanvas_add_task` or include in `kanvas_create_plan`)
|
|
292
|
+
- [ ] If `requires_human_approval=true`, **don't act** — leave a comment with your proposed approach and wait
|
|
293
|
+
- [ ] Move tasks `pending → in_progress → done` one at a time, in sequence order
|
|
294
|
+
- [ ] Comment when something non-obvious happens
|
|
295
|
+
- [ ] Block + comment if you can't proceed
|
|
296
|
+
- [ ] Set a useful `output` JSON before flipping to `done`
|
|
297
|
+
- [ ] Don't refuse silently — if you won't do something, leave a comment explaining why
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Reference — the Kanvas tools you'll use
|
|
302
|
+
|
|
303
|
+
| What you want to do | Tool |
|
|
304
|
+
|---|---|
|
|
305
|
+
| Find work assigned to me | `kanvas_list_my_plans` |
|
|
306
|
+
| Read full plan context | `kanvas_get_plan` |
|
|
307
|
+
| Create a new plan (or sub-plan) | `kanvas_create_plan` |
|
|
308
|
+
| Add a task | `kanvas_add_task` |
|
|
309
|
+
| Start / finish / block a task | `kanvas_update_task_status` |
|
|
310
|
+
| Start / block / fail / done a plan | `kanvas_update_plan` |
|
|
311
|
+
| Approve / reject a plan | `kanvas_approve_plan` |
|
|
312
|
+
| Delete a plan or task | `kanvas_delete_plan` / `kanvas_delete_task` |
|
|
313
|
+
| Read messages on the Activities channel | `kanvas_list_channel_messages({ channel_slug: <plan.uuid> })` |
|
|
314
|
+
| Post a comment | `kanvas_create_message({ channel_slug: <plan.uuid>, message_verb: "comment", message, is_public: 1 })` |
|
|
315
|
+
| List my granted capabilities | `kanvas_list_agent_capabilities` |
|
|
316
|
+
|
|
317
|
+
If a tool doesn't exist for what you need to do, that's a signal — comment on the Activities channel that you need the human to grant you a new capability or assign the task to a different agent.
|