@jiggai/recipes 0.4.19 → 0.4.21
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/docs/WORKFLOW_FIXES_2026-03-13.md +23 -0
- package/index.ts +130 -29
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/recipes/default/business-team.md +28 -28
- package/recipes/default/customer-support-team.md +26 -26
- package/recipes/default/development-team.md +46 -46
- package/recipes/default/marketing-team.md +87 -99
- package/recipes/default/product-team.md +28 -28
- package/recipes/default/research-team.md +26 -26
- package/recipes/default/social-team.md +44 -44
- package/recipes/default/workflow-runner-addon.md +3 -3
- package/recipes/default/writing-team.md +26 -26
- package/src/lib/workflows/workflow-queue.ts +116 -85
- package/src/lib/workflows/workflow-runner.ts +110 -123
- package/src/lib/workspace.ts +20 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Workflow fixes (2026-03-13)
|
|
2
|
+
|
|
3
|
+
This note summarizes the workflow reliability fixes merged on 2026-03-13.
|
|
4
|
+
|
|
5
|
+
## Kitchen (workflow editor)
|
|
6
|
+
- Persist real approval binding ids when saving workflows.
|
|
7
|
+
- Prevents saving synthetic ids like `telegram:dm:<peer>`.
|
|
8
|
+
- Ensures `meta.approvalBindingId` and the `human_approval` node config stay in sync.
|
|
9
|
+
|
|
10
|
+
## Recipes / workflow runner
|
|
11
|
+
### Approval ingestion
|
|
12
|
+
- Robust parsing of approval replies (plain text and formatted/wrapped text).
|
|
13
|
+
- Use the runtime hook name (`message_received`) and scan the canonical workspace root so approval lookup works even when the inbound message is routed to a role workspace (e.g. `workspace-<teamId>/roles/copywriter`).
|
|
14
|
+
|
|
15
|
+
### Queue / worker durability
|
|
16
|
+
- Skip stale recovered tasks so expired claim recovery cannot replay old nodes on runs that already advanced.
|
|
17
|
+
|
|
18
|
+
### Worker cron execution
|
|
19
|
+
- Removed `tools.deny: ["exec"]` from the default `marketing-team` recipe so isolated worker loops can actually execute `worker-tick` commands.
|
|
20
|
+
|
|
21
|
+
## Known remaining issues / follow-ups
|
|
22
|
+
- Completion Telegram notifier is still somewhat intermittent. When it fires, it includes the X URL and runId.
|
|
23
|
+
- If it remains flaky, likely fix is to add a deterministic notification node (workflow-level) or a runner-level `run.completed` notifier that uses the `post_to_platforms` artifact URLs.
|
package/index.ts
CHANGED
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
executeWorkspaceCleanup,
|
|
53
53
|
planWorkspaceCleanup,
|
|
54
54
|
} from "./src/lib/cleanup-workspaces";
|
|
55
|
-
import { resolveWorkspaceRoot } from "./src/lib/workspace";
|
|
55
|
+
import { resolveCanonicalWorkspaceRoot, resolveWorkspaceRoot } from "./src/lib/workspace";
|
|
56
56
|
|
|
57
57
|
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
58
58
|
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
@@ -62,6 +62,73 @@ function asString(v: unknown, fallback = ''): string {
|
|
|
62
62
|
return typeof v === 'string' ? v : (v == null ? fallback : String(v));
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
function extractEventText(evt: Record<string, unknown>, ctx: Record<string, unknown>, metadata: Record<string, unknown>): string {
|
|
66
|
+
const msg = isRecord(evt["message"]) ? (evt["message"] as Record<string, unknown>) : {};
|
|
67
|
+
const parts = Array.isArray(msg["content"]) ? (msg["content"] as unknown[]) : [];
|
|
68
|
+
const texts = parts
|
|
69
|
+
.map((part) => (isRecord(part) ? asString(part["text"]).trim() : ""))
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
if (texts.length) return texts.join("\n").trim();
|
|
72
|
+
|
|
73
|
+
const direct = [
|
|
74
|
+
evt["content"],
|
|
75
|
+
ctx["content"],
|
|
76
|
+
evt["text"],
|
|
77
|
+
evt["body"],
|
|
78
|
+
metadata["content"],
|
|
79
|
+
metadata["text"],
|
|
80
|
+
metadata["message"],
|
|
81
|
+
]
|
|
82
|
+
.map((v) => asString(v).trim())
|
|
83
|
+
.filter(Boolean);
|
|
84
|
+
if (direct.length) return direct.join("\n");
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type ApprovalReply = {
|
|
89
|
+
approved: boolean;
|
|
90
|
+
code: string;
|
|
91
|
+
note?: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function parseApprovalReply(text: string): ApprovalReply | null {
|
|
95
|
+
const raw = String(text ?? '');
|
|
96
|
+
const lines = raw
|
|
97
|
+
.split(/\r?\n/)
|
|
98
|
+
.map((line) => line.trim())
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.reverse();
|
|
101
|
+
|
|
102
|
+
for (const line of lines) {
|
|
103
|
+
const m = line.match(/\b(approve|decline)\b\s+([A-Z0-9]{4,8})(?:\s+(.+))?$/i);
|
|
104
|
+
if (!m) continue;
|
|
105
|
+
const verb = String(m[1] ?? '').toLowerCase();
|
|
106
|
+
const code = String(m[2] ?? '').toUpperCase();
|
|
107
|
+
const note = asString(m[3]).trim();
|
|
108
|
+
return {
|
|
109
|
+
approved: verb === 'approve',
|
|
110
|
+
code,
|
|
111
|
+
...(note ? { note } : {}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const fallback = raw.match(/\b(approve|decline)\b\s+([A-Z0-9]{4,8})(?:\s+(.+))?/i);
|
|
116
|
+
if (!fallback) return null;
|
|
117
|
+
const verb = String(fallback[1] ?? '').toLowerCase();
|
|
118
|
+
const code = String(fallback[2] ?? '').toUpperCase();
|
|
119
|
+
const note = asString(fallback[3]).trim();
|
|
120
|
+
return {
|
|
121
|
+
approved: verb === 'approve',
|
|
122
|
+
code,
|
|
123
|
+
...(note ? { note } : {}),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function shouldProcessApprovalReply(channelHints: string[]): boolean {
|
|
128
|
+
if (!channelHints.length) return true;
|
|
129
|
+
return channelHints.some((v) => v.includes("telegram"));
|
|
130
|
+
}
|
|
131
|
+
|
|
65
132
|
const recipesPlugin = {
|
|
66
133
|
id: "recipes",
|
|
67
134
|
name: "Recipes",
|
|
@@ -73,35 +140,60 @@ const recipesPlugin = {
|
|
|
73
140
|
},
|
|
74
141
|
register(api: OpenClawPluginApi) {
|
|
75
142
|
// Auto-approval via chat reply (MVP):
|
|
76
|
-
// If a human replies `approve <
|
|
143
|
+
// If a human replies `approve <code>` or `decline <code>` in the bound channel,
|
|
77
144
|
// record the decision and resume the run.
|
|
78
|
-
|
|
79
|
-
"message_received" as never,
|
|
80
|
-
async (evt: unknown) => {
|
|
145
|
+
const approvalReplyHandler = async (evt: unknown, ctx: unknown) => {
|
|
81
146
|
try {
|
|
82
147
|
const e = isRecord(evt) ? evt : {};
|
|
83
|
-
const
|
|
84
|
-
const
|
|
148
|
+
const c = isRecord(ctx) ? ctx : {};
|
|
149
|
+
const metadata = isRecord(e["metadata"]) ? (e["metadata"] as Record<string, unknown>) : {};
|
|
150
|
+
const text = extractEventText(e, c, metadata);
|
|
85
151
|
if (!text) return;
|
|
86
152
|
|
|
87
|
-
|
|
88
|
-
if (
|
|
153
|
+
const reply = parseApprovalReply(text);
|
|
154
|
+
if (!reply) return;
|
|
89
155
|
|
|
90
|
-
const
|
|
91
|
-
if (!m) return;
|
|
92
|
-
const verb = String(m[1] ?? "").toLowerCase();
|
|
93
|
-
const code = String(m[2] ?? "").toUpperCase();
|
|
94
|
-
const approved = verb === "approve";
|
|
95
|
-
|
|
96
|
-
const workspaceRoot = resolveWorkspaceRoot(api);
|
|
156
|
+
const workspaceRoot = resolveCanonicalWorkspaceRoot(api);
|
|
97
157
|
const parent = path.resolve(workspaceRoot, "..");
|
|
158
|
+
const roots = Array.from(new Set([parent, workspaceRoot, path.join(workspaceRoot, "workspace")]));
|
|
159
|
+
const channelHints = [
|
|
160
|
+
c["channelId"],
|
|
161
|
+
c["channel"],
|
|
162
|
+
metadata["channelId"],
|
|
163
|
+
metadata["channel"],
|
|
164
|
+
metadata["provider"],
|
|
165
|
+
metadata["source"],
|
|
166
|
+
e["messageProvider"],
|
|
167
|
+
e["channelId"],
|
|
168
|
+
e["channel"],
|
|
169
|
+
e["source"],
|
|
170
|
+
]
|
|
171
|
+
.map((v) => asString(v).toLowerCase())
|
|
172
|
+
.filter(Boolean);
|
|
173
|
+
const isTelegram = shouldProcessApprovalReply(channelHints);
|
|
98
174
|
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
.
|
|
175
|
+
// Only reject when the event explicitly identifies a different channel.
|
|
176
|
+
// Some Telegram inbound payloads arrive without provider/channel metadata.
|
|
177
|
+
if (!isTelegram) {
|
|
178
|
+
console.error(`[recipes] approval reply ignored: non-telegram channel hints=${JSON.stringify(channelHints)} text=${JSON.stringify(text)}`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
103
181
|
|
|
104
|
-
|
|
182
|
+
const { approved, code, note } = reply;
|
|
183
|
+
|
|
184
|
+
const teamDirs: string[] = [];
|
|
185
|
+
for (const root of roots) {
|
|
186
|
+
try {
|
|
187
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
188
|
+
for (const d of entries) {
|
|
189
|
+
if (d.isDirectory() && d.name.startsWith("workspace-")) teamDirs.push(path.join(root, d.name));
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
// ignore root read errors
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let found: { teamId: string; runId: string; approvalPath: string } | null = null;
|
|
105
197
|
|
|
106
198
|
for (const teamDir of teamDirs) {
|
|
107
199
|
const teamId = path.basename(teamDir).replace(/^workspace-/, "");
|
|
@@ -120,8 +212,8 @@ const recipesPlugin = {
|
|
|
120
212
|
try {
|
|
121
213
|
const raw = await fs.readFile(approvalPath, "utf8");
|
|
122
214
|
const a = JSON.parse(raw) as { code?: string; status?: string; runId?: string; teamId?: string };
|
|
123
|
-
if (String(a?.code ?? "").toUpperCase() === code && String(a?.status ?? "") === "pending") {
|
|
124
|
-
found = { teamId: String(a?.teamId ?? teamId), runId: String(a?.runId ?? runId) };
|
|
215
|
+
if (String(a?.code ?? "").trim().toUpperCase() === code && String(a?.status ?? "") === "pending") {
|
|
216
|
+
found = { teamId: String(a?.teamId ?? teamId), runId: String(a?.runId ?? runId), approvalPath };
|
|
125
217
|
break;
|
|
126
218
|
}
|
|
127
219
|
} catch {
|
|
@@ -131,10 +223,14 @@ const recipesPlugin = {
|
|
|
131
223
|
if (found) break;
|
|
132
224
|
}
|
|
133
225
|
|
|
134
|
-
if (!found)
|
|
226
|
+
if (!found) {
|
|
227
|
+
console.error(`[recipes] approval reply not matched: code=${code} text=${JSON.stringify(text)} hints=${JSON.stringify(channelHints)} roots=${JSON.stringify(roots)}`);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
135
230
|
|
|
136
|
-
|
|
137
|
-
|
|
231
|
+
console.error(`[recipes] approval reply matched: code=${code} team=${found.teamId} run=${found.runId} path=${found.approvalPath} approved=${approved}`);
|
|
232
|
+
const approvalNote = note || `${approved ? 'Approved' : 'Declined'} via Telegram (${code})`;
|
|
233
|
+
await handleWorkflowsApprove(api, { teamId: found.teamId, runId: found.runId, approved, note: approvalNote });
|
|
138
234
|
try {
|
|
139
235
|
await handleWorkflowsResume(api, { teamId: found.teamId, runId: found.runId });
|
|
140
236
|
} catch {
|
|
@@ -143,9 +239,11 @@ const recipesPlugin = {
|
|
|
143
239
|
} catch (e) {
|
|
144
240
|
console.error(`[recipes] approval reply handler error: ${(e as Error).message}`);
|
|
145
241
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
api.on("message_received" as never, approvalReplyHandler as never, { priority: 50 } as unknown as { priority: number });
|
|
245
|
+
api.on("message:received" as never, approvalReplyHandler as never, { priority: 50 } as unknown as { priority: number });
|
|
246
|
+
|
|
149
247
|
|
|
150
248
|
// On plugin load, ensure multi-agent config has an explicit agents.list with main at top.
|
|
151
249
|
// This is idempotent and only writes if a change is required.
|
|
@@ -933,6 +1031,9 @@ workflows
|
|
|
933
1031
|
|
|
934
1032
|
// Internal helpers used by unit tests. Not part of the public plugin API.
|
|
935
1033
|
export const __internal = {
|
|
1034
|
+
extractEventText,
|
|
1035
|
+
parseApprovalReply,
|
|
1036
|
+
shouldProcessApprovalReply,
|
|
936
1037
|
ensureMainFirstInAgentsList,
|
|
937
1038
|
upsertBindingInConfig,
|
|
938
1039
|
removeBindingsInConfig,
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -68,16 +68,16 @@ templates:
|
|
|
68
68
|
|
|
69
69
|
## Where to write things
|
|
70
70
|
- Ticket = source of truth for a unit of work.
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
71
|
+
- `../notes/plan.md` + `../shared-context/priorities.md` are **lead-curated**.
|
|
72
|
+
- `../notes/status.md` is **append-only** and updated after each work session (3–5 bullets).
|
|
73
|
+
- `../shared-context/agent-outputs/` is **append-only** logs/output.
|
|
74
74
|
|
|
75
75
|
## End-of-session checklist (everyone)
|
|
76
76
|
After meaningful work:
|
|
77
77
|
1) Update the ticket with what changed + how to verify + rollback.
|
|
78
78
|
2) Add a dated note in the ticket `## Comments`.
|
|
79
|
-
3) Append 3–5 bullets to
|
|
80
|
-
4) Append logs/output to
|
|
79
|
+
3) Append 3–5 bullets to `../notes/status.md`.
|
|
80
|
+
4) Append logs/output to `../shared-context/agent-outputs/`.
|
|
81
81
|
|
|
82
82
|
sharedContext.plan: |
|
|
83
83
|
# Plan (lead-curated)
|
|
@@ -110,16 +110,16 @@ templates:
|
|
|
110
110
|
- `roles/<role>/memory/YYYY-MM-DD.md` (daily log)
|
|
111
111
|
|
|
112
112
|
## Plan vs status (team coordination)
|
|
113
|
-
-
|
|
114
|
-
-
|
|
113
|
+
- `../notes/plan.md` + `../shared-context/priorities.md` are lead-curated
|
|
114
|
+
- `../notes/status.md` is append-only roll-up (everyone appends)
|
|
115
115
|
|
|
116
116
|
## Outputs / artifacts
|
|
117
117
|
- `roles/<role>/agent-outputs/` (append-only)
|
|
118
|
-
-
|
|
118
|
+
- `../shared-context/agent-outputs/` (team-level, read/write from role via `../`)
|
|
119
119
|
|
|
120
120
|
## Role work loop contract (safe-idle)
|
|
121
121
|
- No-op unless explicit queued work exists for the role.
|
|
122
|
-
- If work happens, write back in order: ticket →
|
|
122
|
+
- If work happens, write back in order: ticket → `../notes/status.md` → `roles/<role>/agent-outputs/`.
|
|
123
123
|
|
|
124
124
|
sharedContext.priorities: |
|
|
125
125
|
# Priorities (lead-curated)
|
|
@@ -172,24 +172,24 @@ templates:
|
|
|
172
172
|
|
|
173
173
|
Before you act:
|
|
174
174
|
1) Read:
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
175
|
+
- `../notes/plan.md`
|
|
176
|
+
- `../notes/status.md`
|
|
177
|
+
- `../shared-context/priorities.md`
|
|
178
178
|
- the relevant ticket(s)
|
|
179
179
|
|
|
180
180
|
After you act:
|
|
181
181
|
1) Write back:
|
|
182
182
|
- Update tickets with decisions/assignments.
|
|
183
|
-
- Keep
|
|
183
|
+
- Keep `../notes/status.md` current (3–5 bullets per active ticket).
|
|
184
184
|
|
|
185
185
|
## Curator model
|
|
186
186
|
|
|
187
187
|
You are the curator of:
|
|
188
|
-
-
|
|
189
|
-
-
|
|
188
|
+
- `../notes/plan.md`
|
|
189
|
+
- `../shared-context/priorities.md`
|
|
190
190
|
|
|
191
191
|
Everyone else should append to:
|
|
192
|
-
-
|
|
192
|
+
- `../shared-context/agent-outputs/` (append-only)
|
|
193
193
|
- `shared-context/feedback/`
|
|
194
194
|
|
|
195
195
|
Your job is to periodically distill those inputs into the curated files.
|
|
@@ -204,8 +204,8 @@ templates:
|
|
|
204
204
|
- `work/in-progress/` — tickets currently being executed
|
|
205
205
|
- `work/testing/` — tickets awaiting QA verification
|
|
206
206
|
- `work/done/` — completed tickets + completion notes
|
|
207
|
-
-
|
|
208
|
-
-
|
|
207
|
+
- `../notes/plan.md` — current plan / priorities (curated)
|
|
208
|
+
- `../notes/status.md` — current status snapshot
|
|
209
209
|
- `shared-context/` — shared context + append-only outputs
|
|
210
210
|
|
|
211
211
|
### Ticket numbering (critical)
|
|
@@ -222,8 +222,8 @@ templates:
|
|
|
222
222
|
|
|
223
223
|
### Your responsibilities
|
|
224
224
|
- For every new request in `inbox/`, create a normalized ticket in `work/backlog/`.
|
|
225
|
-
- Curate
|
|
226
|
-
- Keep
|
|
225
|
+
- Curate `../notes/plan.md` and `../shared-context/priorities.md`.
|
|
226
|
+
- Keep `../notes/status.md` updated.
|
|
227
227
|
- When work is ready for QA, move the ticket to `work/testing/` and assign it to the tester.
|
|
228
228
|
- Only after QA verification, move the ticket to `work/done/` (or use `openclaw recipes complete`).
|
|
229
229
|
- When a completion appears in `work/done/`, write a short summary into `outbox/`.
|
|
@@ -244,8 +244,8 @@ templates:
|
|
|
244
244
|
## Guardrails (read → act → write)
|
|
245
245
|
Before you act:
|
|
246
246
|
1) Read:
|
|
247
|
-
-
|
|
248
|
-
-
|
|
247
|
+
- `../notes/plan.md`
|
|
248
|
+
- `../notes/status.md`
|
|
249
249
|
- relevant ticket(s) in `work/in-progress/`
|
|
250
250
|
- any relevant shared context under `shared-context/`
|
|
251
251
|
|
|
@@ -274,8 +274,8 @@ templates:
|
|
|
274
274
|
## Guardrails (read → act → write)
|
|
275
275
|
Before you act:
|
|
276
276
|
1) Read:
|
|
277
|
-
-
|
|
278
|
-
-
|
|
277
|
+
- `../notes/plan.md`
|
|
278
|
+
- `../notes/status.md`
|
|
279
279
|
- relevant ticket(s) in `work/in-progress/`
|
|
280
280
|
- any relevant shared context under `shared-context/`
|
|
281
281
|
|
|
@@ -304,8 +304,8 @@ templates:
|
|
|
304
304
|
## Guardrails (read → act → write)
|
|
305
305
|
Before you act:
|
|
306
306
|
1) Read:
|
|
307
|
-
-
|
|
308
|
-
-
|
|
307
|
+
- `../notes/plan.md`
|
|
308
|
+
- `../notes/status.md`
|
|
309
309
|
- relevant ticket(s) in `work/in-progress/`
|
|
310
310
|
- any relevant shared context under `shared-context/`
|
|
311
311
|
|
|
@@ -334,8 +334,8 @@ templates:
|
|
|
334
334
|
## Guardrails (read → act → write)
|
|
335
335
|
Before you act:
|
|
336
336
|
1) Read:
|
|
337
|
-
-
|
|
338
|
-
-
|
|
337
|
+
- `../notes/plan.md`
|
|
338
|
+
- `../notes/status.md`
|
|
339
339
|
- relevant ticket(s) in `work/in-progress/`
|
|
340
340
|
- any relevant shared context under `shared-context/`
|
|
341
341
|
|
|
@@ -57,16 +57,16 @@ templates:
|
|
|
57
57
|
|
|
58
58
|
## Where to write things
|
|
59
59
|
- Ticket = source of truth for a unit of work.
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
60
|
+
- `../notes/plan.md` + `../shared-context/priorities.md` are **lead-curated**.
|
|
61
|
+
- `../notes/status.md` is **append-only** and updated after each work session (3–5 bullets).
|
|
62
|
+
- `../shared-context/agent-outputs/` is **append-only** logs/output.
|
|
63
63
|
|
|
64
64
|
## End-of-session checklist (everyone)
|
|
65
65
|
After meaningful work:
|
|
66
66
|
1) Update the ticket with what changed + how to verify + rollback.
|
|
67
67
|
2) Add a dated note in the ticket `## Comments`.
|
|
68
|
-
3) Append 3–5 bullets to
|
|
69
|
-
4) Append logs/output to
|
|
68
|
+
3) Append 3–5 bullets to `../notes/status.md`.
|
|
69
|
+
4) Append logs/output to `../shared-context/agent-outputs/`.
|
|
70
70
|
|
|
71
71
|
sharedContext.plan: |
|
|
72
72
|
# Plan (lead-curated)
|
|
@@ -99,16 +99,16 @@ templates:
|
|
|
99
99
|
- `roles/<role>/memory/YYYY-MM-DD.md` (daily log)
|
|
100
100
|
|
|
101
101
|
## Plan vs status (team coordination)
|
|
102
|
-
-
|
|
103
|
-
-
|
|
102
|
+
- `../notes/plan.md` + `../shared-context/priorities.md` are lead-curated
|
|
103
|
+
- `../notes/status.md` is append-only roll-up (everyone appends)
|
|
104
104
|
|
|
105
105
|
## Outputs / artifacts
|
|
106
106
|
- `roles/<role>/agent-outputs/` (append-only)
|
|
107
|
-
-
|
|
107
|
+
- `../shared-context/agent-outputs/` (team-level, read/write from role via `../`)
|
|
108
108
|
|
|
109
109
|
## Role work loop contract (safe-idle)
|
|
110
110
|
- No-op unless explicit queued work exists for the role.
|
|
111
|
-
- If work happens, write back in order: ticket →
|
|
111
|
+
- If work happens, write back in order: ticket → `../notes/status.md` → `roles/<role>/agent-outputs/`.
|
|
112
112
|
|
|
113
113
|
sharedContext.priorities: |
|
|
114
114
|
# Priorities (lead-curated)
|
|
@@ -141,24 +141,24 @@ templates:
|
|
|
141
141
|
|
|
142
142
|
Before you act:
|
|
143
143
|
1) Read:
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
144
|
+
- `../notes/plan.md`
|
|
145
|
+
- `../notes/status.md`
|
|
146
|
+
- `../shared-context/priorities.md`
|
|
147
147
|
- the relevant ticket(s)
|
|
148
148
|
|
|
149
149
|
After you act:
|
|
150
150
|
1) Write back:
|
|
151
151
|
- Update tickets with decisions/assignments.
|
|
152
|
-
- Keep
|
|
152
|
+
- Keep `../notes/status.md` current (3–5 bullets per active ticket).
|
|
153
153
|
|
|
154
154
|
## Curator model
|
|
155
155
|
|
|
156
156
|
You are the curator of:
|
|
157
|
-
-
|
|
158
|
-
-
|
|
157
|
+
- `../notes/plan.md`
|
|
158
|
+
- `../shared-context/priorities.md`
|
|
159
159
|
|
|
160
160
|
Everyone else should append to:
|
|
161
|
-
-
|
|
161
|
+
- `../shared-context/agent-outputs/` (append-only)
|
|
162
162
|
- `shared-context/feedback/`
|
|
163
163
|
|
|
164
164
|
Your job is to periodically distill those inputs into the curated files.
|
|
@@ -173,8 +173,8 @@ templates:
|
|
|
173
173
|
- `work/in-progress/` — tickets currently being executed
|
|
174
174
|
- `work/testing/` — tickets awaiting QA verification
|
|
175
175
|
- `work/done/` — completed tickets + completion notes
|
|
176
|
-
-
|
|
177
|
-
-
|
|
176
|
+
- `../notes/plan.md` — current plan / priorities (curated)
|
|
177
|
+
- `../notes/status.md` — current status snapshot
|
|
178
178
|
- `shared-context/` — shared context + append-only outputs
|
|
179
179
|
|
|
180
180
|
### Ticket numbering (critical)
|
|
@@ -191,8 +191,8 @@ templates:
|
|
|
191
191
|
|
|
192
192
|
### Your responsibilities
|
|
193
193
|
- For every new request in `inbox/`, create a normalized ticket in `work/backlog/`.
|
|
194
|
-
- Curate
|
|
195
|
-
- Keep
|
|
194
|
+
- Curate `../notes/plan.md` and `../shared-context/priorities.md`.
|
|
195
|
+
- Keep `../notes/status.md` updated.
|
|
196
196
|
- When work is ready for QA, move the ticket to `work/testing/` and assign it to the tester.
|
|
197
197
|
- Only after QA verification, move the ticket to `work/done/` (or use `openclaw recipes complete`).
|
|
198
198
|
- When a completion appears in `work/done/`, write a short summary into `outbox/`.
|
|
@@ -216,8 +216,8 @@ templates:
|
|
|
216
216
|
## Guardrails (read → act → write)
|
|
217
217
|
Before you act:
|
|
218
218
|
1) Read:
|
|
219
|
-
-
|
|
220
|
-
-
|
|
219
|
+
- `../notes/plan.md`
|
|
220
|
+
- `../notes/status.md`
|
|
221
221
|
- relevant ticket(s) in `work/in-progress/`
|
|
222
222
|
- any relevant shared context under `shared-context/`
|
|
223
223
|
|
|
@@ -246,8 +246,8 @@ templates:
|
|
|
246
246
|
## Guardrails (read → act → write)
|
|
247
247
|
Before you act:
|
|
248
248
|
1) Read:
|
|
249
|
-
-
|
|
250
|
-
-
|
|
249
|
+
- `../notes/plan.md`
|
|
250
|
+
- `../notes/status.md`
|
|
251
251
|
- relevant ticket(s) in `work/in-progress/`
|
|
252
252
|
- any relevant shared context under `shared-context/`
|
|
253
253
|
|
|
@@ -276,8 +276,8 @@ templates:
|
|
|
276
276
|
## Guardrails (read → act → write)
|
|
277
277
|
Before you act:
|
|
278
278
|
1) Read:
|
|
279
|
-
-
|
|
280
|
-
-
|
|
279
|
+
- `../notes/plan.md`
|
|
280
|
+
- `../notes/status.md`
|
|
281
281
|
- relevant ticket(s) in `work/in-progress/`
|
|
282
282
|
- any relevant shared context under `shared-context/`
|
|
283
283
|
|