Skip to content
File

Blob: src/workflows.ts

typescript412 lines
1import type { AgentId } from "./store.js";
2 
3// ── Types ──────────────────────────────────────────────────────────
4 
5export type WorkflowId =
6 | "custom"
7 | "research"
8 | "implementation"
9 | "implementation_codex_architect";
10 
11export interface WorkflowPhase {
12 id: string;
13 name: string;
14 instructions: Partial<Record<AgentId, string>>;
15 reminderForReceiver: Partial<Record<AgentId, string>>;
16}
17 
18export interface SubagentHint {
19 modelDirective?: string;
20 waitDirective?: string;
21 extraRelayRules?: string[];
22}
23 
24export interface WorkflowRoleSpec {
25 contract: string[];
26 stopConditions: string[];
27 startInstruction?: string;
28 flowDesc?: string;
29 subagentHint?: SubagentHint;
30}
31 
32export interface WorkflowTemplate {
33 id: WorkflowId;
34 name: string;
35 description: string;
36 goalPlaceholder?: string;
37 /** If set, the session's initiator is pinned to this agent and the UI should hide its selector. */
38 forcedInitiator?: AgentId;
39 phases: WorkflowPhase[];
40 byRole: Record<AgentId, WorkflowRoleSpec>;
41}
42 
43export interface BuildContext {
44 collabId: string;
45 endpoint: string;
46 agent: AgentId;
47 otherAgent: AgentId;
48 role: "INITIATOR" | "RESPONDER";
49 goal: string;
50 phases: WorkflowPhase[];
51 currentPhaseIndex: number;
52}
53 
54export interface WorkflowSummary {
55 id: WorkflowId;
56 name: string;
57 description: string;
58 goalPlaceholder?: string;
59 forcedInitiator?: AgentId;
60 phases: Array<{ id: string; name: string }>;
61}
62 
63// ── Templates ──────────────────────────────────────────────────────
64 
65const custom: WorkflowTemplate = {
66 id: "custom",
67 name: "Custom (no contract)",
68 description:
69 "Freeform collaboration. The goal field is passed straight through with the generic role prompts. Use this when you want to paste your own contract into the goal.",
70 goalPlaceholder: "Describe the shared goal for the two agents...",
71 phases: [],
72 byRole: {
73 claude: { contract: [], stopConditions: [] },
74 codex: { contract: [], stopConditions: [] },
75 },
76};
77 
78const relaySubagentHint: SubagentHint = {
79 modelDirective:
80 "When spawning the wait-relay subagent, set its model to `gpt-5.4 low` (pass it as the `model` argument on the `spawn_agent` call). The relay is a low-cost message wrapper.",
81 waitDirective:
82 "When you call `wait_agent` to await the relay subagent's result, use a long polling window. The relay internally long-polls `collab_wait_for_reply` for up to 5 minutes (300s), so set the `wait_agent` timeout to at least 360s -- or use the maximum 3600s to avoid any fast-poll / re-wake churn. Do NOT use short timeouts that force repeated wake-ups while the relay is still waiting.",
83};
84 
85const stateMatrixRule =
86 "Before approving implementation, require an explicit correctness model whenever correctness depends on multiple modes, time ordering, async recovery, permissions, cache/state ownership, range/coordinate mapping, ownership boundaries, or cross-boundary coordination. The plan must state the invariant in one precise sentence, provide the state/transition matrix, derive any ordering or mapping-bias choices from that invariant, and name the focused tests that prove the important cells. Do not approve a lifecycle-only matrix that names phases without proving the edge cases.";
87 
88const systematicAuditRule =
89 "If one review round finds multiple material issues in the same subsystem or behavior, or if two consecutive review rounds find material issues in the same subsystem or behavior, stop patch-by-patch churn. Before the next implementation pass, collaborate on the invariant/state matrix and require one systematic end-to-end audit of that subsystem.";
90 
91const research: WorkflowTemplate = {
92 id: "research",
93 name: "Research (Claude synthesizes, Codex assists)",
94 description:
95 "Read-only research collaboration. Claude opens and owns the final synthesis; Codex investigates complementary areas and challenges assumptions.",
96 goalPlaceholder:
97 "Describe the research question, sources or repo areas to inspect, and the expected final deliverable...",
98 forcedInitiator: "claude",
99 phases: [],
100 byRole: {
101 claude: {
102 contract: [
103 "You own the final synthesis and presentation to the user.",
104 "Begin with a substantive research brief: current findings, open questions, and the complementary areas you want Codex to investigate.",
105 "This workflow is read-only. Do not edit code or project files unless the user's goal explicitly asks for a written artifact; if an artifact is requested, you own producing it after the collaboration has converged.",
106 "Ask Codex to verify claims, surface risks, and challenge weak assumptions rather than duplicate your exact work.",
107 "When you have enough input, send a closing message that says no further Codex action is needed, then call `collab_close_session`.",
108 ],
109 stopConditions: [
110 "You have enough information to produce the final user-facing synthesis.",
111 "User explicitly tells you to stop.",
112 ],
113 startInstruction:
114 "Begin by researching enough context to avoid a blind handoff, then send Codex one substantive opening research brief with specific complementary questions. Do not edit files.",
115 },
116 codex: {
117 contract: [
118 "You assist Claude's research and do not own the final user-facing synthesis.",
119 "This workflow is read-only. Do not edit code or project files.",
120 "Investigate complementary areas, verify claims against source material, surface risks, and push back on weak assumptions.",
121 "Do not duplicate Claude's exact research scope unless Claude explicitly asks for independent verification.",
122 "After every message, spawn the next wait-relay subagent unless Claude explicitly closes the research loop or the session is closed.",
123 ],
124 stopConditions: [
125 "Claude explicitly says no further Codex action is needed.",
126 "The session is closed.",
127 "User explicitly dismisses you.",
128 ],
129 subagentHint: relaySubagentHint,
130 flowDesc:
131 "Message order: wait -> send -> wait -> send -> ... (you receive first). After EVERY `collab_send_message`, spawn a new wait-relay subagent unless Claude explicitly closes the research loop or the session is closed.",
132 startInstruction:
133 "Your FIRST action: spawn a wait-relay subagent to listen for Claude's opening research brief. While it waits, do not edit files; prepare to investigate complementary areas once Claude's message arrives.",
134 },
135 },
136};
137 
138const implementation: WorkflowTemplate = {
139 id: "implementation",
140 name: "Implementation (plan -> confirm -> code -> review loop)",
141 description:
142 "Claude plans with Codex, pauses for user confirmation, implements, then loops with Codex on review until no material findings remain. Claude always initiates.",
143 goalPlaceholder:
144 "Describe the implementation task (what to build, constraints, where the code lives)...",
145 forcedInitiator: "claude",
146 phases: [
147 {
148 id: "plan",
149 name: "Collaborative Plan",
150 instructions: {
151 claude:
152 "Discuss approach with Codex. Propose scope and trade-offs. DO NOT begin implementing yet. When planning converges, pause and wait for explicit user confirmation before advancing to the implement phase.",
153 codex:
154 "Plan immediately without waiting. Push back on scope; surface risks. DO NOT implement. After sending each message, spawn a wait subagent and stay in the loop -- do not end your turn.",
155 },
156 reminderForReceiver: {
157 claude:
158 "PHASE: plan. Stay collaborative. No code yet. If the plan has meaningful state, async, permission, ownership, or coordinate/range complexity, require an invariant plus state/transition matrix before implementation approval. Pause for user confirmation before transitioning to implement.",
159 codex:
160 "PHASE: plan. Advisory only. Do not edit files. If the plan has meaningful state, async, permission, ownership, or coordinate/range complexity, require an invariant plus state/transition matrix before implementation approval. After you reply, immediately spawn the next wait-relay subagent. Do not end your turn without a wait subagent running.",
161 },
162 },
163 {
164 id: "implement",
165 name: "Claude Implements",
166 instructions: {
167 claude:
168 "Implement the agreed plan. Run the project formatter, run relevant validation for touched code, stage changes with `git add` (do NOT commit). Then ask Codex to review the staged diff.",
169 codex:
170 "You are NOT implementing. Spawn a wait subagent and keep listening for Claude's review request. STANDBY = actively waiting via a wait subagent, not idle.",
171 },
172 reminderForReceiver: {
173 claude:
174 "PHASE: implement. You own the edits. Run formatter + validation. Stage with git add, do not commit. Then ask Codex to review.",
175 codex:
176 "PHASE: implement. Do not touch files. You should currently be running a wait-relay subagent. If this message arrived, that wait subagent just completed -- after you respond (or if no response is warranted), spawn a new wait subagent immediately. Do not end your turn.",
177 },
178 },
179 {
180 id: "review",
181 name: "Review Loop",
182 instructions: {
183 claude:
184 "Receive Codex's review. Address material findings (bugs, regressions, contract violations). Re-run formatter + validation, re-stage, and ask for re-review. Loop until Codex reports no material findings.",
185 codex:
186 "Code-review mode. Inspect the staged diff. Prioritize bugs and behavioral regressions over style. Use file:line specificity. Reply with findings ordered by severity, or 'no material findings' if clean. IMMEDIATELY after sending your review, spawn a new wait-relay subagent to listen for Claude's next iteration -- reporting findings is NOT a session terminator.",
187 },
188 reminderForReceiver: {
189 claude:
190 "PHASE: review. Address material findings only. Defer style nits. If review finds multiple or repeated material issues in the same subsystem, pause patching and collaborate on the invariant/state matrix plus a systematic audit before more edits. Re-stage and re-request review until clean.",
191 codex:
192 "PHASE: review. Read the staged diff. Focus on correctness, regressions, missing tests. file:line specific. If one round has multiple same-subsystem material findings, or repeated rounds hit the same subsystem, require an invariant/state-matrix audit before the next patch. No material findings => say so explicitly. After you reply with your review, spawn the next wait-relay subagent IMMEDIATELY to await Claude's re-staged diff. Do NOT say 'I'm in standby' and stop -- say it and spawn the subagent in the same turn.",
193 },
194 },
195 ],
196 byRole: {
197 claude: {
198 contract: [
199 "You PLAN collaboratively with Codex before implementing anything.",
200 stateMatrixRule,
201 "You PAUSE and wait for explicit user confirmation before transitioning from plan to implement.",
202 "You implement the agreed plan, run the project formatter, run relevant validation for touched code, and stage changes with `git add` (no commits).",
203 "You ask Codex to review the staged diff and address material findings until Codex reports none.",
204 systematicAuditRule,
205 "Bugs and behavioral regressions block; style nits are deferrable.",
206 "Do NOT ask the user again during the implementation/review loop unless blocked or the agreed plan materially changes.",
207 ],
208 stopConditions: [
209 "Codex reports 'no material findings' on the latest staged diff.",
210 "User explicitly tells you to stop or commit.",
211 ],
212 startInstruction:
213 "Begin by collaborating with Codex on a plan. Send a substantive opening message proposing scope, trade-offs, and open questions. Do NOT begin implementing during planning.",
214 },
215 codex: {
216 contract: [
217 "CRITICAL LOOP RULE: After sending ANY message via `collab_send_message`, your IMMEDIATE next action is to spawn a new wait-relay subagent to listen for Claude's next message. NEVER end a turn without an active wait subagent running. NEVER stop to wait for user input -- the user is not driving this loop, Claude is.",
218 "STANDBY in this workflow means 'actively listening for Claude via a spawned wait-relay subagent' -- it does NOT mean idle, does NOT mean ending your turn, does NOT mean waiting for further user prompting. If you find yourself about to say 'I'm in standby' or 'waiting for the next diff', that sentence MUST be followed by spawning a wait subagent in the same turn.",
219 "You do NOT implement. Claude owns all file edits.",
220 "During the plan phase, push back on scope and surface risks. Start collaborating immediately without waiting for user confirmation.",
221 stateMatrixRule,
222 "Do not edit files during planning and do not make unsolicited code changes during review unless Claude explicitly asks for a narrow, scoped helper task.",
223 "When Claude asks for review after implementation begins, switch to code-review mode and inspect ONLY the staged diff or exact change set Claude identifies.",
224 "Prioritize bugs, behavioral regressions, missing tests, and risky assumptions. Use file:line specificity. Defer style nits unless they materially affect correctness.",
225 systematicAuditRule,
226 "Report findings first, ordered by severity. If you have no material findings, say so explicitly and mention any residual risks or test gaps. THEN immediately spawn the next wait subagent -- reporting findings is NOT a session terminator.",
227 "Keep reviewing on each re-request until you have no material findings.",
228 "The session is complete ONLY when Claude explicitly says implementation and review are finished, or the user explicitly dismisses you. Until then, every sent message is followed by a spawned wait subagent.",
229 ],
230 stopConditions: [
231 "You report 'no material findings' on Claude's latest staged diff AND Claude confirms the session is done.",
232 "Claude explicitly tells you the session is complete.",
233 "User explicitly dismisses you.",
234 ],
235 subagentHint: relaySubagentHint,
236 flowDesc:
237 "Message order: wait -> send -> wait -> send -> ... (you receive first). This loop is CONTINUOUS and has no natural terminator until Claude or the user explicitly ends it. After EVERY `collab_send_message`, your immediate next action is ALWAYS to spawn a new wait-relay subagent. Never end a turn without an active wait subagent. Do not treat 'I sent my reply' as a stopping point -- the loop continues.",
238 startInstruction:
239 "Your FIRST action: spawn a wait-relay subagent to listen for Claude's opening message. Then, in parallel in your main context, propose your own independent implementation plan and push back on scope. Do NOT implement. Every time your wait subagent returns with a message, you reply AND immediately spawn the next wait subagent -- no exceptions, no 'standby without a subagent'.",
240 },
241 },
242};
243 
244const implementationCodexArchitect: WorkflowTemplate = {
245 id: "implementation_codex_architect",
246 name: "Implementation (Codex architecture -> Claude plan -> human gate -> code -> review)",
247 description:
248 "Codex proposes the architecture, Claude independently explores the repo before responding and then authors the implementation plan, Codex approves it, the user gates the start of coding, then Claude implements and Codex reviews until clean.",
249 goalPlaceholder:
250 "Describe the implementation task (what to build, constraints, where the code lives)...",
251 forcedInitiator: "codex",
252 phases: [
253 {
254 id: "architecture",
255 name: "Architecture",
256 instructions: {
257 codex:
258 "Explore the repo and propose the architecture first. Own the design discussion, surface trade-offs, and refine the architecture with Claude. DO NOT implement or author the step-by-step implementation plan yet.",
259 claude:
260 "Before your first substantive reply, independently explore the codebase so you are not responding blind. Then critique Codex's architecture with repo-aware constraints, edge cases, and integration concerns. DO NOT implement.",
261 },
262 reminderForReceiver: {
263 codex:
264 "PHASE: architecture. You own the design proposal and iteration. No code edits, no implementation plan yet.",
265 claude:
266 "PHASE: architecture. Independently inspect the codebase before replying if you have not already. Provide repo-aware feedback only. No code edits.",
267 },
268 },
269 {
270 id: "implementation_plan",
271 name: "Implementation Plan",
272 instructions: {
273 codex:
274 "Review Claude's concrete implementation plan against the agreed architecture. Approve it explicitly if it is ready, or send blocking corrections if it is not. DO NOT let coding start without your approval and the later user confirmation.",
275 claude:
276 "Write the concrete implementation plan for this repo: touched areas, sequencing, validation, and test intent. Get explicit Codex approval. After Codex approves, PAUSE and wait for explicit user confirmation before transitioning to implement.",
277 },
278 reminderForReceiver: {
279 codex:
280 "PHASE: implementation_plan. Approve or block Claude's repo-specific implementation plan. If correctness depends on state, async, permission, ownership, or coordinate/range behavior, block approval until the plan includes an invariant, state/transition matrix, any mapping/order derivation, and focused validation. No code edits. Coding must not start until you approve and the user advances the phase.",
281 claude:
282 "PHASE: implementation_plan. Author the concrete repo-specific implementation plan. If correctness depends on state, async, permission, ownership, or coordinate/range behavior, include an invariant, state/transition matrix, any mapping/order derivation, and focused validation. No code edits. After Codex approves, pause for explicit user confirmation before moving to implement.",
283 },
284 },
285 {
286 id: "implement",
287 name: "Claude Implements",
288 instructions: {
289 codex:
290 "You are NOT implementing. Stay available for clarification while Claude edits. STANDBY = actively waiting via a wait subagent, not idle.",
291 claude:
292 "Implement the approved plan only after the user explicitly confirms the transition into this phase. Run the project formatter, run relevant validation for touched code, stage changes with `git add` (do NOT commit). Then ask Codex to review the staged diff.",
293 },
294 reminderForReceiver: {
295 codex:
296 "PHASE: implement. Do not touch files unless Claude explicitly asks for a narrow scoped helper task. Keep the wait-relay loop active.",
297 claude:
298 "PHASE: implement. The user gate has been cleared. You own the edits. Run formatter + validation. Stage with git add, do not commit. Then ask Codex to review.",
299 },
300 },
301 {
302 id: "review",
303 name: "Review Loop",
304 instructions: {
305 codex:
306 "Code-review mode. Inspect the staged diff. Prioritize bugs and behavioral regressions over style. Use file:line specificity. Reply with findings ordered by severity, or 'no material findings' if clean. IMMEDIATELY after sending your review, spawn a new wait-relay subagent to listen for Claude's next iteration -- reporting findings is NOT a session terminator.",
307 claude:
308 "Receive Codex's review. Address material findings (bugs, regressions, contract violations). Re-run formatter + validation, re-stage, and ask for re-review. Loop until Codex reports no material findings.",
309 },
310 reminderForReceiver: {
311 codex:
312 "PHASE: review. Read the staged diff. Focus on correctness, regressions, missing tests. file:line specific. If one round has multiple same-subsystem material findings, or repeated rounds hit the same subsystem, require an invariant/state-matrix audit before the next patch. No material findings => say so explicitly. After you reply, spawn the next wait-relay subagent immediately.",
313 claude:
314 "PHASE: review. Address material findings only. Defer style nits. If review finds multiple or repeated material issues in the same subsystem, pause patching and collaborate on the invariant/state matrix plus a systematic audit before more edits. Re-stage and re-request review until clean.",
315 },
316 },
317 ],
318 byRole: {
319 claude: {
320 contract: [
321 "Before your first substantive reply, independently explore the codebase enough to ground your feedback in the actual repo.",
322 "During the architecture phase, critique and refine Codex's design with concrete repo-aware constraints, edge cases, and execution risks.",
323 "During the implementation_plan phase, you author the concrete implementation plan for this repo and get explicit Codex approval before code starts.",
324 stateMatrixRule,
325 "After Codex approves the implementation plan, you PAUSE and wait for explicit user confirmation before transitioning to implementation.",
326 "You implement the approved plan, run the project formatter, run relevant validation for touched code, and stage changes with `git add` (no commits).",
327 "You ask Codex to review the staged diff and address material findings until Codex reports none.",
328 systematicAuditRule,
329 "Bugs and behavioral regressions block; style nits are deferrable.",
330 "Do NOT ask the user again during the implementation/review loop unless blocked or the agreed plan materially changes.",
331 ],
332 stopConditions: [
333 "Codex reports 'no material findings' on the latest staged diff.",
334 "User explicitly tells you to stop or commit.",
335 ],
336 startInstruction:
337 "Begin NOW by spawning a subagent to call `collab_wait_for_reply` with lastSeenMessageId=0 so you can receive Codex's opening architecture proposal. While that subagent waits, independently explore the codebase and prepare repo-aware feedback. Do NOT send a blind first response based only on the incoming proposal.",
338 },
339 codex: {
340 contract: [
341 "CRITICAL LOOP RULE: After sending ANY message via `collab_send_message`, your IMMEDIATE next action is to spawn a new wait-relay subagent to listen for Claude's next message. NEVER end a turn without an active wait subagent running.",
342 "STANDBY in this workflow means 'actively listening for Claude via a spawned wait-relay subagent' -- it does NOT mean idle, does NOT mean ending your turn, and does NOT mean waiting for further user prompting without a subagent.",
343 "You own the architecture during the architecture phase. Start by exploring the repo and then sending one substantive architecture proposal.",
344 "You do NOT implement. Claude owns all file edits.",
345 "During the implementation_plan phase, require a concrete repo-specific plan from Claude. Approve it explicitly if ready, or return blocking corrections if it is not.",
346 stateMatrixRule,
347 "Coding must not start until you approve Claude's implementation plan AND the user explicitly advances to the implement phase.",
348 "Do not edit files during architecture or implementation planning and do not make unsolicited code changes during review unless Claude explicitly asks for a narrow, scoped helper task.",
349 "When Claude asks for review after implementation begins, switch to code-review mode and inspect ONLY the staged diff or exact change set Claude identifies.",
350 "Prioritize bugs, behavioral regressions, missing tests, and risky assumptions. Use file:line specificity. Defer style nits unless they materially affect correctness.",
351 systematicAuditRule,
352 "Report findings first, ordered by severity. If you have no material findings, say so explicitly and mention any residual risks or test gaps. THEN immediately spawn the next wait subagent -- reporting findings is NOT a session terminator.",
353 "Keep reviewing on each re-request until you have no material findings.",
354 "The session is complete ONLY when Claude explicitly says implementation and review are finished, or the user explicitly dismisses you. Until then, every sent message is followed by a spawned wait subagent.",
355 ],
356 stopConditions: [
357 "You report 'no material findings' on Claude's latest staged diff AND Claude confirms the session is done.",
358 "Claude explicitly tells you the session is complete.",
359 "User explicitly dismisses you.",
360 ],
361 subagentHint: relaySubagentHint,
362 startInstruction:
363 "Begin by exploring the codebase and researching the goal. Once you have enough context, call `collab_send_message` with one substantive architecture proposal that covers trade-offs, risks, and open questions. Do NOT send the step-by-step implementation plan yet. Then spawn a wait-relay subagent for Claude's reply.",
364 },
365 },
366};
367 
368// ── Registry ───────────────────────────────────────────────────────
369 
370export const WORKFLOWS: Readonly<Record<WorkflowId, WorkflowTemplate>> = Object.freeze({
371 custom,
372 research,
373 implementation,
374 implementation_codex_architect: implementationCodexArchitect,
375});
376 
377const VALID_IDS: readonly WorkflowId[] = [
378 "custom",
379 "research",
380 "implementation",
381 "implementation_codex_architect",
382];
383 
384export function isValidWorkflowId(id: unknown): id is WorkflowId {
385 return typeof id === "string" && (VALID_IDS as readonly string[]).includes(id);
386}
387 
388export function getWorkflow(id: string | undefined): WorkflowTemplate {
389 if (id && isValidWorkflowId(id)) return WORKFLOWS[id];
390 return WORKFLOWS.custom;
391}
392 
393export function getPhase(t: WorkflowTemplate, i: number): WorkflowPhase | undefined {
394 if (i < 0 || i >= t.phases.length) return undefined;
395 return t.phases[i];
396}
397 
398export function listValidWorkflowIds(): readonly WorkflowId[] {
399 return VALID_IDS;
400}
401 
402export function summarizeWorkflows(): WorkflowSummary[] {
403 return (Object.values(WORKFLOWS) as WorkflowTemplate[]).map((t) => ({
404 id: t.id,
405 name: t.name,
406 description: t.description,
407 goalPlaceholder: t.goalPlaceholder,
408 forcedInitiator: t.forcedInitiator,
409 phases: t.phases.map((p) => ({ id: p.id, name: p.name })),
410 }));
411}