wogiflow 2.7.1 → 2.9.0
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/.claude/commands/wogi-audit.md +156 -8
- package/.claude/commands/wogi-start.md +276 -0
- package/lib/workspace-channel-server.js +19 -4
- package/lib/workspace-gates.js +87 -0
- package/lib/workspace-routing.js +15 -5
- package/lib/workspace-session.js +308 -0
- package/lib/workspace.js +49 -3
- package/package.json +1 -1
- package/scripts/flow-audit-gates.js +766 -0
- package/scripts/flow-config-defaults.js +27 -4
- package/scripts/flow-config-migrate.js +270 -0
- package/scripts/flow-context-manifest.js +322 -0
- package/scripts/flow-done-gates.js +76 -0
- package/scripts/flow-done.js +14 -0
- package/scripts/flow-gate-latch.js +119 -0
- package/scripts/flow-runtime-verification.js +782 -0
- package/scripts/hooks/core/post-compact.js +11 -1
- package/scripts/hooks/core/session-context.js +51 -7
- package/scripts/hooks/core/task-completed.js +26 -0
- package/scripts/postinstall.js +20 -0
package/lib/workspace-routing.js
CHANGED
|
@@ -222,7 +222,9 @@ After completing the task:
|
|
|
222
222
|
agentConfig: {
|
|
223
223
|
description: `${repoName}: ${task.substring(0, 50)}...`,
|
|
224
224
|
// Named subagent (2.1.88+): shows in @ mention typeahead as @repoName
|
|
225
|
-
name: repoName
|
|
225
|
+
name: repoName,
|
|
226
|
+
// Propagate reasoning effort from manager to worker
|
|
227
|
+
...(process.env.WOGI_EFFORT_LEVEL && { model_options: { effort: process.env.WOGI_EFFORT_LEVEL } })
|
|
226
228
|
}
|
|
227
229
|
};
|
|
228
230
|
}
|
|
@@ -704,9 +706,11 @@ function checkWorkerHealth(port) {
|
|
|
704
706
|
* @param {string} workspaceRoot
|
|
705
707
|
* @param {string} repoName — target repo
|
|
706
708
|
* @param {string} taskId — task ID to start
|
|
709
|
+
* @param {Object} [opts] — dispatch options
|
|
710
|
+
* @param {string} [opts.effortLevel] — reasoning effort to propagate ('low'|'medium'|'high')
|
|
707
711
|
* @returns {Promise<{ ok: boolean, message: string }>}
|
|
708
712
|
*/
|
|
709
|
-
async function dispatchToChannel(workspaceRoot, repoName, taskId) {
|
|
713
|
+
async function dispatchToChannel(workspaceRoot, repoName, taskId, opts = {}) {
|
|
710
714
|
// Validate taskId format to prevent injection into channel body
|
|
711
715
|
if (!/^wf-[0-9a-f]{8}$/i.test(taskId)) {
|
|
712
716
|
return { ok: false, message: `Invalid task ID format: "${taskId}" — expected wf-XXXXXXXX` };
|
|
@@ -738,10 +742,16 @@ async function dispatchToChannel(workspaceRoot, repoName, taskId) {
|
|
|
738
742
|
};
|
|
739
743
|
}
|
|
740
744
|
|
|
741
|
-
// Dispatch the task
|
|
742
|
-
|
|
745
|
+
// Dispatch the task with effort level propagation
|
|
746
|
+
// When the manager uses ultrathink/high effort, workers should too
|
|
747
|
+
const VALID_EFFORTS = new Set(['low', 'medium', 'high']);
|
|
748
|
+
const rawEffort = opts.effortLevel || process.env.WOGI_EFFORT_LEVEL || '';
|
|
749
|
+
const effortLevel = VALID_EFFORTS.has(rawEffort) ? rawEffort : '';
|
|
750
|
+
const effortPrefix = effortLevel ? `[effort:${effortLevel}] ` : '';
|
|
751
|
+
const dispatchBody = `${effortPrefix}/wogi-start ${taskId}`;
|
|
752
|
+
const result = await httpPost('127.0.0.1', port, dispatchBody);
|
|
743
753
|
if (result.ok) {
|
|
744
|
-
return { ok: true, message: `Dispatched /wogi-start ${taskId} to ${repoName} (port ${port})` };
|
|
754
|
+
return { ok: true, message: `Dispatched /wogi-start ${taskId} to ${repoName} (port ${port})${effortLevel ? ` [effort: ${effortLevel}]` : ''}` };
|
|
745
755
|
}
|
|
746
756
|
|
|
747
757
|
return { ok: false, message: `Dispatch failed: HTTP ${result.status} — ${result.body}` };
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Workspace — Manager Session Handoff
|
|
5
|
+
*
|
|
6
|
+
* The workspace manager doesn't have WogiFlow installed locally
|
|
7
|
+
* (it orchestrates, doesn't code). So /wogi-session-end fails.
|
|
8
|
+
*
|
|
9
|
+
* This module provides workspace-aware session management:
|
|
10
|
+
* - saveManagerHandoff() — captures session state for next session
|
|
11
|
+
* - loadManagerHandoff() — restores state at session start
|
|
12
|
+
*
|
|
13
|
+
* The handoff document (.workspace/state/manager-session.json) includes:
|
|
14
|
+
* - Dispatched tasks summary (what was sent to which worker)
|
|
15
|
+
* - Pending/completed workspace tasks
|
|
16
|
+
* - Unread worker messages
|
|
17
|
+
* - Active locks
|
|
18
|
+
* - Last sync timestamp
|
|
19
|
+
* - Session notes (what was discussed, decisions made)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
|
|
27
|
+
const { WORKSPACE_CONFIG_FILE, WORKSPACE_DIR } = require('./workspace');
|
|
28
|
+
|
|
29
|
+
// ============================================================
|
|
30
|
+
// Manager Detection
|
|
31
|
+
// ============================================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Detect if the current session is a workspace manager.
|
|
35
|
+
* The manager is the session running in the workspace root directory
|
|
36
|
+
* (where wogi-workspace.json lives), NOT in a member repo.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} [cwd]
|
|
39
|
+
* @returns {{ isManager: boolean, workspaceRoot: string|null }}
|
|
40
|
+
*/
|
|
41
|
+
function isWorkspaceManager(cwd) {
|
|
42
|
+
const dir = cwd || process.cwd();
|
|
43
|
+
const configPath = path.join(dir, WORKSPACE_CONFIG_FILE);
|
|
44
|
+
|
|
45
|
+
if (fs.existsSync(configPath)) {
|
|
46
|
+
// Check this is the root, not a member repo that happens to have the config
|
|
47
|
+
// The manager's cwd IS the workspace root
|
|
48
|
+
return { isManager: true, workspaceRoot: dir };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { isManager: false, workspaceRoot: null };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ============================================================
|
|
55
|
+
// Session Handoff — Save
|
|
56
|
+
// ============================================================
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Save a manager session handoff document.
|
|
60
|
+
* Called when the manager session ends (or user says "wrap up").
|
|
61
|
+
*
|
|
62
|
+
* @param {string} workspaceRoot
|
|
63
|
+
* @param {Object} [options]
|
|
64
|
+
* @param {string} [options.sessionNotes] — free-text notes about what was discussed
|
|
65
|
+
* @param {string[]} [options.decisionsM made] — decisions made during this session
|
|
66
|
+
* @returns {Object} the saved handoff document
|
|
67
|
+
*/
|
|
68
|
+
function saveManagerHandoff(workspaceRoot, options = {}) {
|
|
69
|
+
const handoff = {
|
|
70
|
+
savedAt: new Date().toISOString(),
|
|
71
|
+
workspaceName: '',
|
|
72
|
+
members: {},
|
|
73
|
+
dispatched: [],
|
|
74
|
+
pendingTasks: [],
|
|
75
|
+
completedTasks: [],
|
|
76
|
+
unreadMessages: [],
|
|
77
|
+
activeLocks: [],
|
|
78
|
+
contractDrifts: [],
|
|
79
|
+
lastSyncAt: null,
|
|
80
|
+
sessionNotes: options.sessionNotes || '',
|
|
81
|
+
decisions: options.decisions || []
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Read workspace config
|
|
85
|
+
const configPath = path.join(workspaceRoot, WORKSPACE_CONFIG_FILE);
|
|
86
|
+
try {
|
|
87
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
88
|
+
handoff.workspaceName = config.name || '';
|
|
89
|
+
|
|
90
|
+
// Read each member's task status
|
|
91
|
+
for (const [name, memberConfig] of Object.entries(config.members || {})) {
|
|
92
|
+
const memberPath = path.resolve(workspaceRoot, memberConfig.path);
|
|
93
|
+
const readyPath = path.join(memberPath, '.workflow', 'state', 'ready.json');
|
|
94
|
+
|
|
95
|
+
const memberStatus = {
|
|
96
|
+
role: memberConfig.role,
|
|
97
|
+
inProgress: [],
|
|
98
|
+
ready: [],
|
|
99
|
+
recentlyCompleted: []
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
if (fs.existsSync(readyPath)) {
|
|
104
|
+
const ready = JSON.parse(fs.readFileSync(readyPath, 'utf-8'));
|
|
105
|
+
memberStatus.inProgress = (ready.inProgress || []).map(t => ({ id: t.id, title: t.title }));
|
|
106
|
+
memberStatus.ready = (ready.ready || []).map(t => ({ id: t.id, title: t.title }));
|
|
107
|
+
memberStatus.recentlyCompleted = (ready.recentlyCompleted || []).slice(0, 5).map(t => ({ id: t.id, title: t.title }));
|
|
108
|
+
}
|
|
109
|
+
} catch (_err) {
|
|
110
|
+
// Non-critical
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
handoff.members[name] = memberStatus;
|
|
114
|
+
}
|
|
115
|
+
} catch (_err) {
|
|
116
|
+
// Config read failure — save what we can
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Read workspace-level tasks
|
|
120
|
+
const wsReadyPath = path.join(workspaceRoot, WORKSPACE_DIR, 'state', 'ready.json');
|
|
121
|
+
try {
|
|
122
|
+
if (fs.existsSync(wsReadyPath)) {
|
|
123
|
+
const wsReady = JSON.parse(fs.readFileSync(wsReadyPath, 'utf-8'));
|
|
124
|
+
handoff.pendingTasks = (wsReady.ready || []).map(t => ({ id: t.id, title: t.title }));
|
|
125
|
+
handoff.completedTasks = (wsReady.recentlyCompleted || []).slice(0, 10).map(t => ({ id: t.id, title: t.title }));
|
|
126
|
+
}
|
|
127
|
+
} catch (_err) {
|
|
128
|
+
// Non-critical
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Read unread messages
|
|
132
|
+
try {
|
|
133
|
+
const messagesDir = path.join(workspaceRoot, WORKSPACE_DIR, 'messages');
|
|
134
|
+
if (fs.existsSync(messagesDir)) {
|
|
135
|
+
const files = fs.readdirSync(messagesDir).filter(f => f.endsWith('.json'));
|
|
136
|
+
for (const file of files) {
|
|
137
|
+
try {
|
|
138
|
+
const msg = JSON.parse(fs.readFileSync(path.join(messagesDir, file), 'utf-8'));
|
|
139
|
+
if (msg.status === 'pending') {
|
|
140
|
+
handoff.unreadMessages.push({
|
|
141
|
+
id: msg.id,
|
|
142
|
+
from: msg.from,
|
|
143
|
+
type: msg.type,
|
|
144
|
+
subject: msg.subject,
|
|
145
|
+
timestamp: msg.timestamp
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
} catch (_err) {
|
|
149
|
+
// Skip malformed
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (_err) {
|
|
154
|
+
// Non-critical
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Read active locks
|
|
158
|
+
try {
|
|
159
|
+
const locksDir = path.join(workspaceRoot, WORKSPACE_DIR, 'state', 'locks');
|
|
160
|
+
if (fs.existsSync(locksDir)) {
|
|
161
|
+
const now = Date.now();
|
|
162
|
+
const files = fs.readdirSync(locksDir).filter(f => f.endsWith('.json'));
|
|
163
|
+
for (const file of files) {
|
|
164
|
+
try {
|
|
165
|
+
const lock = JSON.parse(fs.readFileSync(path.join(locksDir, file), 'utf-8'));
|
|
166
|
+
if (new Date(lock.expiresAt).getTime() > now) {
|
|
167
|
+
handoff.activeLocks.push({
|
|
168
|
+
interface: lock.interface,
|
|
169
|
+
owner: lock.owner,
|
|
170
|
+
expiresAt: lock.expiresAt
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
} catch (_err) {
|
|
174
|
+
// Skip
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch (_err) {
|
|
179
|
+
// Non-critical
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Check manifest freshness
|
|
183
|
+
const manifestPath = path.join(workspaceRoot, WORKSPACE_DIR, 'state', 'workspace-manifest.json');
|
|
184
|
+
try {
|
|
185
|
+
if (fs.existsSync(manifestPath)) {
|
|
186
|
+
const stat = fs.statSync(manifestPath);
|
|
187
|
+
handoff.lastSyncAt = stat.mtime.toISOString();
|
|
188
|
+
}
|
|
189
|
+
} catch (_err) {
|
|
190
|
+
// Non-critical
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Write the handoff document
|
|
194
|
+
const handoffPath = path.join(workspaceRoot, WORKSPACE_DIR, 'state', 'manager-session.json');
|
|
195
|
+
fs.mkdirSync(path.dirname(handoffPath), { recursive: true });
|
|
196
|
+
fs.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
197
|
+
|
|
198
|
+
return handoff;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ============================================================
|
|
202
|
+
// Session Handoff — Load (for session start)
|
|
203
|
+
// ============================================================
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Load the previous manager session handoff.
|
|
207
|
+
* Called at session start to restore context.
|
|
208
|
+
*
|
|
209
|
+
* @param {string} workspaceRoot
|
|
210
|
+
* @returns {Object|null} handoff document or null if none exists
|
|
211
|
+
*/
|
|
212
|
+
function loadManagerHandoff(workspaceRoot) {
|
|
213
|
+
const handoffPath = path.join(workspaceRoot, WORKSPACE_DIR, 'state', 'manager-session.json');
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
if (fs.existsSync(handoffPath)) {
|
|
217
|
+
return JSON.parse(fs.readFileSync(handoffPath, 'utf-8'));
|
|
218
|
+
}
|
|
219
|
+
} catch (_err) {
|
|
220
|
+
// Non-critical
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Format the handoff document as a readable session start briefing.
|
|
228
|
+
*
|
|
229
|
+
* @param {Object} handoff — from loadManagerHandoff()
|
|
230
|
+
* @returns {string} formatted briefing
|
|
231
|
+
*/
|
|
232
|
+
function formatHandoffBriefing(handoff) {
|
|
233
|
+
if (!handoff) return 'No previous session handoff found.';
|
|
234
|
+
|
|
235
|
+
const lines = [];
|
|
236
|
+
const age = Math.round((Date.now() - new Date(handoff.savedAt).getTime()) / (60 * 60 * 1000));
|
|
237
|
+
|
|
238
|
+
lines.push('Previous Session Handoff');
|
|
239
|
+
lines.push('━'.repeat(40));
|
|
240
|
+
lines.push(`Saved: ${handoff.savedAt} (${age}h ago)`);
|
|
241
|
+
lines.push(`Workspace: ${handoff.workspaceName}`);
|
|
242
|
+
lines.push('');
|
|
243
|
+
|
|
244
|
+
// Member status
|
|
245
|
+
lines.push('Member Status:');
|
|
246
|
+
for (const [name, status] of Object.entries(handoff.members || {})) {
|
|
247
|
+
const inProgress = status.inProgress?.length || 0;
|
|
248
|
+
const ready = status.ready?.length || 0;
|
|
249
|
+
lines.push(` ${name} (${status.role}): ${inProgress} in-progress, ${ready} ready`);
|
|
250
|
+
for (const t of status.inProgress || []) {
|
|
251
|
+
lines.push(` >> ${t.id}: ${t.title}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
lines.push('');
|
|
255
|
+
|
|
256
|
+
// Unread messages
|
|
257
|
+
if (handoff.unreadMessages?.length > 0) {
|
|
258
|
+
lines.push(`Unread Messages (${handoff.unreadMessages.length}):`);
|
|
259
|
+
for (const msg of handoff.unreadMessages) {
|
|
260
|
+
lines.push(` [${msg.type}] from ${msg.from}: ${msg.subject}`);
|
|
261
|
+
}
|
|
262
|
+
lines.push('');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Active locks
|
|
266
|
+
if (handoff.activeLocks?.length > 0) {
|
|
267
|
+
lines.push(`Active Locks (${handoff.activeLocks.length}):`);
|
|
268
|
+
for (const lock of handoff.activeLocks) {
|
|
269
|
+
lines.push(` ${lock.interface} — held by ${lock.owner} (expires: ${lock.expiresAt})`);
|
|
270
|
+
}
|
|
271
|
+
lines.push('');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Session notes
|
|
275
|
+
if (handoff.sessionNotes) {
|
|
276
|
+
lines.push('Session Notes:');
|
|
277
|
+
lines.push(` ${handoff.sessionNotes}`);
|
|
278
|
+
lines.push('');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Decisions
|
|
282
|
+
if (handoff.decisions?.length > 0) {
|
|
283
|
+
lines.push('Decisions Made:');
|
|
284
|
+
for (const d of handoff.decisions) {
|
|
285
|
+
lines.push(` - ${d}`);
|
|
286
|
+
}
|
|
287
|
+
lines.push('');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Manifest freshness
|
|
291
|
+
if (handoff.lastSyncAt) {
|
|
292
|
+
const syncAge = Math.round((Date.now() - new Date(handoff.lastSyncAt).getTime()) / (60 * 60 * 1000));
|
|
293
|
+
lines.push(`Last sync: ${syncAge}h ago${syncAge > 24 ? ' (STALE — run flow workspace sync)' : ''}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return lines.join('\n');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ============================================================
|
|
300
|
+
// Exports
|
|
301
|
+
// ============================================================
|
|
302
|
+
|
|
303
|
+
module.exports = {
|
|
304
|
+
isWorkspaceManager,
|
|
305
|
+
saveManagerHandoff,
|
|
306
|
+
loadManagerHandoff,
|
|
307
|
+
formatHandoffBriefing
|
|
308
|
+
};
|
package/lib/workspace.js
CHANGED
|
@@ -575,9 +575,45 @@ ${memberLines.join('\n')}
|
|
|
575
575
|
## Session Startup Checklist
|
|
576
576
|
|
|
577
577
|
When you start a session, do this FIRST:
|
|
578
|
-
1. Read \`.workspace/state/
|
|
579
|
-
2.
|
|
580
|
-
3.
|
|
578
|
+
1. **Check for previous session handoff**: Read \`.workspace/state/manager-session.json\` if it exists — this tells you what happened in the last session, what's pending, and what the user was working on. Display the briefing to the user.
|
|
579
|
+
2. Read \`.workspace/state/workspace-manifest.json\` — understand the current integration map
|
|
580
|
+
3. Check \`.workspace/messages/\` for unread messages (status: "pending") — show them to the user
|
|
581
|
+
4. Read each member's \`.workflow/state/ready.json\` — know what tasks are in progress
|
|
582
|
+
5. If manifest is stale (>24h): suggest \`flow workspace sync\`
|
|
583
|
+
|
|
584
|
+
## Session End (Manager Handoff)
|
|
585
|
+
|
|
586
|
+
When the user says "wrap up", "session end", "that's all":
|
|
587
|
+
|
|
588
|
+
**You are the workspace manager — you don't have /wogi-session-end.** Instead, save a handoff document:
|
|
589
|
+
|
|
590
|
+
1. **Collect session state**:
|
|
591
|
+
- What tasks were dispatched to which workers (and their status)
|
|
592
|
+
- Current state of each member's ready.json (in-progress, ready, completed)
|
|
593
|
+
- Unread messages from workers
|
|
594
|
+
- Active locks on shared interfaces
|
|
595
|
+
- Manifest freshness (when was last sync?)
|
|
596
|
+
|
|
597
|
+
2. **Ask the user**: "Any session notes or decisions to carry forward?"
|
|
598
|
+
|
|
599
|
+
3. **Save handoff** to \`.workspace/state/manager-session.json\`:
|
|
600
|
+
\`\`\`bash
|
|
601
|
+
# Read current workspace state and save
|
|
602
|
+
cat .workspace/state/workspace-manifest.json | head -5 # Check freshness
|
|
603
|
+
ls -t .workspace/messages/*.json 2>/dev/null | head -10 # Unread messages
|
|
604
|
+
\`\`\`
|
|
605
|
+
|
|
606
|
+
4. **Write the JSON handoff** with: savedAt, members (per-repo task status), unreadMessages, activeLocks, sessionNotes, decisions, lastSyncAt
|
|
607
|
+
|
|
608
|
+
5. **Display session summary**:
|
|
609
|
+
\`\`\`
|
|
610
|
+
Session Summary:
|
|
611
|
+
Dispatched: N tasks to M repos
|
|
612
|
+
Completed: N tasks
|
|
613
|
+
Pending: N messages, N locks
|
|
614
|
+
Handoff saved to: .workspace/state/manager-session.json
|
|
615
|
+
Next session will resume from this point.
|
|
616
|
+
\`\`\`
|
|
581
617
|
|
|
582
618
|
## How to Route Tasks
|
|
583
619
|
|
|
@@ -645,6 +681,16 @@ ${Object.keys(config.channels?.members || {}).map(name => `- **@${name}-investig
|
|
|
645
681
|
|
|
646
682
|
When spawning agents for delegation, always include the \`name\` field in the Agent config to enable @mention addressing.
|
|
647
683
|
|
|
684
|
+
## Reasoning Effort Propagation
|
|
685
|
+
|
|
686
|
+
When the user requests high reasoning effort (e.g., "ultrathink"), propagate it to workers:
|
|
687
|
+
- Set \`WOGI_EFFORT_LEVEL=high\` environment variable before dispatching
|
|
688
|
+
- The dispatch system automatically prefixes channel messages with \`[effort:high]\`
|
|
689
|
+
- Workers receiving \`[effort:high]\` should apply the same reasoning level to their work
|
|
690
|
+
- This ensures workers don't use lower effort than what the user requested for the manager
|
|
691
|
+
|
|
692
|
+
**In curl dispatches**, prefix the message: \`curl -s -X POST http://localhost:{port} -d "[effort:high] /wogi-start {taskId}"\`
|
|
693
|
+
|
|
648
694
|
## Waiting for Worker Results (CRITICAL — Automatic Return Path)
|
|
649
695
|
|
|
650
696
|
Workers **automatically write results** to \`.workspace/messages/\` when they complete a task. You do NOT need to ask them to report back — the task-completed hook writes a \`task-complete\` message automatically.
|