@kanvas/openclaw-plugin 0.1.16 → 0.1.18
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/dist/domains/deals/index.js +103 -0
- package/dist/domains/deals/types.js +1 -0
- package/dist/domains/events/index.js +157 -0
- package/dist/domains/events/types.js +1 -0
- package/dist/domains/nervousSystem/index.js +203 -0
- package/dist/domains/nervousSystem/types.js +1 -0
- package/dist/domains/orders/index.js +145 -0
- package/dist/domains/orders/types.js +1 -0
- package/dist/index.js +58 -1
- package/dist/tools/deals.js +91 -0
- package/dist/tools/events.js +134 -0
- package/dist/tools/nervousSystem.js +217 -0
- package/dist/tools/orders.js +185 -0
- package/package.json +1 -1
- package/skills/kanvas-nervous-system/SKILL.md +317 -0
package/dist/tools/orders.js
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { toolResult } from "./helpers.js";
|
|
3
|
+
const OrderLineItemInput = Type.Object({
|
|
4
|
+
variant_id: Type.Union([Type.String(), Type.Number()], { description: "Variant ID" }),
|
|
5
|
+
quantity: Type.Number({ description: "Quantity" }),
|
|
6
|
+
price: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
7
|
+
metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
8
|
+
channel_id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
9
|
+
});
|
|
10
|
+
const OrderAddressInput = Type.Object({
|
|
11
|
+
address: Type.String(),
|
|
12
|
+
address_2: Type.Optional(Type.String()),
|
|
13
|
+
city: Type.Optional(Type.String()),
|
|
14
|
+
state: Type.Optional(Type.String()),
|
|
15
|
+
zip: Type.Optional(Type.String()),
|
|
16
|
+
country: Type.Optional(Type.String()),
|
|
17
|
+
is_default: Type.Optional(Type.Boolean()),
|
|
18
|
+
});
|
|
19
|
+
const OrderBillingInput = Type.Object({
|
|
20
|
+
address: Type.String(),
|
|
21
|
+
address2: Type.Optional(Type.String()),
|
|
22
|
+
city: Type.String(),
|
|
23
|
+
state: Type.String(),
|
|
24
|
+
zip: Type.String(),
|
|
25
|
+
country: Type.String(),
|
|
26
|
+
});
|
|
27
|
+
const OrderCustomerInput = Type.Object({
|
|
28
|
+
id: Type.Optional(Type.Union([Type.String(), Type.Number()])),
|
|
29
|
+
firstname: Type.String(),
|
|
30
|
+
lastname: Type.Optional(Type.String()),
|
|
31
|
+
contacts: Type.Optional(Type.Array(Type.Object({
|
|
32
|
+
value: Type.String(),
|
|
33
|
+
contacts_types_id: Type.Number(),
|
|
34
|
+
weight: Type.Optional(Type.Number()),
|
|
35
|
+
}))),
|
|
36
|
+
address: Type.Optional(Type.Array(OrderAddressInput)),
|
|
37
|
+
});
|
|
3
38
|
export function registerOrdersTools(api, service, ensureAuth) {
|
|
39
|
+
// ── Read ───────────────────────────────────────────────────
|
|
4
40
|
api.registerTool({
|
|
5
41
|
name: "kanvas_search_orders",
|
|
6
42
|
label: "Search Orders",
|
|
@@ -26,4 +62,153 @@ export function registerOrdersTools(api, service, ensureAuth) {
|
|
|
26
62
|
return toolResult(await service.getOrder(params.id));
|
|
27
63
|
},
|
|
28
64
|
});
|
|
65
|
+
// ── Lookups ────────────────────────────────────────────────
|
|
66
|
+
api.registerTool({
|
|
67
|
+
name: "kanvas_list_order_statuses",
|
|
68
|
+
label: "List Order Statuses",
|
|
69
|
+
description: "List order statuses. Use to find status slugs for status transitions.",
|
|
70
|
+
parameters: Type.Object({
|
|
71
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
72
|
+
}),
|
|
73
|
+
async execute(_id, params) {
|
|
74
|
+
await ensureAuth();
|
|
75
|
+
return toolResult(await service.listOrderStatuses(params.first));
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
api.registerTool({
|
|
79
|
+
name: "kanvas_list_order_types",
|
|
80
|
+
label: "List Order Types",
|
|
81
|
+
description: "List order types (e.g. standard, subscription). Each type has its own status pipeline.",
|
|
82
|
+
parameters: Type.Object({
|
|
83
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
84
|
+
}),
|
|
85
|
+
async execute(_id, params) {
|
|
86
|
+
await ensureAuth();
|
|
87
|
+
return toolResult(await service.listOrderTypes(params.first));
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
api.registerTool({
|
|
91
|
+
name: "kanvas_list_regions",
|
|
92
|
+
label: "List Regions",
|
|
93
|
+
description: "List regions for order creation. Regions define currency and tax rules.",
|
|
94
|
+
parameters: Type.Object({
|
|
95
|
+
first: Type.Optional(Type.Number({ description: "Max results (default 50)" })),
|
|
96
|
+
}),
|
|
97
|
+
async execute(_id, params) {
|
|
98
|
+
await ensureAuth();
|
|
99
|
+
return toolResult(await service.listRegions(params.first));
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
// ── Write ──────────────────────────────────────────────────
|
|
103
|
+
api.registerTool({
|
|
104
|
+
name: "kanvas_create_draft_order",
|
|
105
|
+
label: "Create Draft Order",
|
|
106
|
+
description: "Create a draft order. Requires email, customer (firstname), region_id, and at least one item with variant_id+quantity. Use kanvas_list_regions to get a region ID and kanvas_list_variants to find variants.",
|
|
107
|
+
parameters: Type.Object({
|
|
108
|
+
email: Type.String({ description: "Customer email" }),
|
|
109
|
+
phone: Type.Optional(Type.String()),
|
|
110
|
+
customer: OrderCustomerInput,
|
|
111
|
+
region_id: Type.Union([Type.String(), Type.Number()], { description: "Region ID (use kanvas_list_regions)" }),
|
|
112
|
+
items: Type.Array(OrderLineItemInput, { description: "Line items" }),
|
|
113
|
+
channel_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: "Channel ID" })),
|
|
114
|
+
billing_address: Type.Optional(OrderBillingInput),
|
|
115
|
+
shipping_address: Type.Optional(OrderAddressInput),
|
|
116
|
+
note: Type.Optional(Type.String()),
|
|
117
|
+
metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
118
|
+
}),
|
|
119
|
+
async execute(_id, params) {
|
|
120
|
+
await ensureAuth();
|
|
121
|
+
return toolResult(await service.createDraftOrder(params));
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
api.registerTool({
|
|
125
|
+
name: "kanvas_update_order",
|
|
126
|
+
label: "Update Order",
|
|
127
|
+
description: "Update an order's items, fulfillment status, payment status, or metadata.",
|
|
128
|
+
parameters: Type.Object({
|
|
129
|
+
id: Type.String({ description: "Order ID" }),
|
|
130
|
+
items: Type.Optional(Type.Array(OrderLineItemInput)),
|
|
131
|
+
fulfillment_status: Type.Optional(Type.String()),
|
|
132
|
+
status: Type.Optional(Type.String()),
|
|
133
|
+
payment_status: Type.Optional(Type.String()),
|
|
134
|
+
metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
135
|
+
metadata_action: Type.Optional(Type.Union([Type.Literal("MERGE"), Type.Literal("REPLACE")])),
|
|
136
|
+
}),
|
|
137
|
+
async execute(_id, params) {
|
|
138
|
+
await ensureAuth();
|
|
139
|
+
const { id, ...input } = params;
|
|
140
|
+
return toolResult(await service.updateOrder(id, input));
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
api.registerTool({
|
|
144
|
+
name: "kanvas_update_draft_order_status",
|
|
145
|
+
label: "Update Draft Order Status",
|
|
146
|
+
description: "Change a draft order's status (PENDING, COMPLETED, DRAFT, CANCELED, FAILED).",
|
|
147
|
+
parameters: Type.Object({
|
|
148
|
+
order_id: Type.String({ description: "Order ID" }),
|
|
149
|
+
status: Type.Union([
|
|
150
|
+
Type.Literal("PENDING"),
|
|
151
|
+
Type.Literal("COMPLETED"),
|
|
152
|
+
Type.Literal("DRAFT"),
|
|
153
|
+
Type.Literal("CANCELED"),
|
|
154
|
+
Type.Literal("FAILED"),
|
|
155
|
+
], { description: "New status" }),
|
|
156
|
+
}),
|
|
157
|
+
async execute(_id, params) {
|
|
158
|
+
await ensureAuth();
|
|
159
|
+
return toolResult(await service.updateDraftOrderStatus(params.order_id, params.status));
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
api.registerTool({
|
|
163
|
+
name: "kanvas_transition_order_status",
|
|
164
|
+
label: "Transition Order Status",
|
|
165
|
+
description: "Move an order through its status pipeline using a status slug (from kanvas_list_order_statuses).",
|
|
166
|
+
parameters: Type.Object({
|
|
167
|
+
order_id: Type.String({ description: "Order ID" }),
|
|
168
|
+
status_slug: Type.Optional(Type.String({ description: "Target status slug" })),
|
|
169
|
+
date: Type.Optional(Type.String({ description: "Transition date" })),
|
|
170
|
+
}),
|
|
171
|
+
async execute(_id, params) {
|
|
172
|
+
await ensureAuth();
|
|
173
|
+
return toolResult(await service.transitionOrderStatus(params.order_id, params.status_slug, params.date));
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
api.registerTool({
|
|
177
|
+
name: "kanvas_order_change_customer",
|
|
178
|
+
label: "Change Order Customer",
|
|
179
|
+
description: "Change the customer on an order. Requires xKanvasKey (app-key authenticated).",
|
|
180
|
+
parameters: Type.Object({
|
|
181
|
+
order_id: Type.String({ description: "Order ID" }),
|
|
182
|
+
customer_id: Type.String({ description: "New customer ID" }),
|
|
183
|
+
}),
|
|
184
|
+
async execute(_id, params) {
|
|
185
|
+
await ensureAuth();
|
|
186
|
+
return toolResult(await service.changeOrderCustomer(params.order_id, params.customer_id));
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
api.registerTool({
|
|
190
|
+
name: "kanvas_delete_order",
|
|
191
|
+
label: "Delete Order",
|
|
192
|
+
description: "Delete an order.",
|
|
193
|
+
parameters: Type.Object({
|
|
194
|
+
id: Type.String({ description: "Order ID" }),
|
|
195
|
+
}),
|
|
196
|
+
async execute(_id, params) {
|
|
197
|
+
await ensureAuth();
|
|
198
|
+
return toolResult(await service.deleteOrder(params.id));
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
api.registerTool({
|
|
202
|
+
name: "kanvas_send_order_email",
|
|
203
|
+
label: "Send Order Email",
|
|
204
|
+
description: "Send an email for an order (confirmation, receipt, etc.). Requires xKanvasKey (app-key authenticated).",
|
|
205
|
+
parameters: Type.Object({
|
|
206
|
+
order_id: Type.String({ description: "Order ID" }),
|
|
207
|
+
template: Type.Optional(Type.String({ description: "Email template name" })),
|
|
208
|
+
}),
|
|
209
|
+
async execute(_id, params) {
|
|
210
|
+
await ensureAuth();
|
|
211
|
+
return toolResult(await service.sendOrderEmail(params.order_id, params.template));
|
|
212
|
+
},
|
|
213
|
+
});
|
|
29
214
|
}
|
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.
|