Created
July 4, 2026 18:20
-
-
Save kcosr/c1fe24b6732707dcf7e4a52e5f3d9c40 to your computer and use it in GitHub Desktop.
formwork-codex-task workflow
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // formwork-codex-task — DRAFT (2026-07-04, unvalidated) | |
| // | |
| // A keel workflow that runs ONE Formwork implementation task with a | |
| // persistent Codex session, then PARKS instead of finishing so the | |
| // orchestrator (Claude, via `keel signal`) can send follow-up rounds to | |
| // the same session. Review is deliberately NOT embedded: the orchestrator | |
| // reviews the branch between rounds and feeds findings back as follow-ups. | |
| // | |
| // Cribbed from workflows/branch-worktree-implement-review-codex-fast. | |
| // Validate with `keel run` against the current SDK before | |
| // `keel workflow save formwork-codex-task <this file>`. | |
| // | |
| // Launch: | |
| // keel workflow launch formwork-codex-task --detach --input '{ | |
| // "task": "<full brief: epic text, spec anchors, VC discipline>", | |
| // "checkArgv": ["npm", "run", "check"] | |
| // }' | |
| // Follow up / close: | |
| // keel signal "$RUN" instruction '{"kind":"follow-up","instructions":"..."}' | |
| // keel signal "$RUN" instruction '{"kind":"done"}' | |
| import type { Ctx } from "@kcosr/keel"; | |
| import { passthrough } from "@kcosr/keel"; | |
| type Instruction = | |
| | { kind: "follow-up"; instructions: string } | |
| | { kind: "done"; note?: string }; | |
| type Input = { | |
| /** Full task brief: epic/task text, spec anchor sections, acceptance | |
| * criteria, and the repo's commit discipline (commit per task to the | |
| * branch; imperative messages; spec/code lockstep). */ | |
| task: string; | |
| /** Check command argv; defaults to ["npm","run","check"]. */ | |
| checkArgv?: string[]; | |
| /** Auto-repair turns per round before parking red. Default 3. */ | |
| maxRepairRounds?: number; | |
| /** Optional workspace setup commands (e.g. install). */ | |
| install?: boolean; | |
| /** | |
| * Codex reasoning effort. Policy: "high" (default) for normal tasks; | |
| * "xhigh" only for genuinely complicated ones (effect journal, FormExpr | |
| * evaluator class of work). | |
| */ | |
| reasoning?: "medium" | "high" | "xhigh"; | |
| /** | |
| * Tool policy for the Codex session. Default "unrestricted" (Kevin's | |
| * call, 2026-07-04): Codex is trusted and may need network access | |
| * (docs, package registries). "workspace-write" re-enables the Codex | |
| * sandbox with network disabled. | |
| */ | |
| toolPolicy?: "workspace-write" | "unrestricted"; | |
| /** | |
| * Codex service tier. Pass "fast" for priority processing on | |
| * time-sensitive tasks; omit to leave Codex defaults in control. | |
| * Requires the keel daemon to run keel >= 462352c (wire value fixed | |
| * from "priority" to "fast"); on older daemons "fast" fails turn | |
| * start with -32600. | |
| */ | |
| serviceTier?: "fast" | "normal"; | |
| }; | |
| type RoundResult = { | |
| label: string; | |
| checkStatus: "green" | "red"; | |
| repairs: number; | |
| summary: unknown; | |
| }; | |
| const Out = passthrough<unknown>(); | |
| export default async function formworkCodexTask(ctx: Ctx, input: Input) { | |
| const checkArgv = input.checkArgv ?? ["npm", "run", "check"]; | |
| const maxRepairs = input.maxRepairRounds ?? 3; | |
| const workspace = await ctx.workspace({ | |
| key: "impl", | |
| mode: "worktree", | |
| branch: true, | |
| // retain-on-failure: failed runs stay resumable (session workspaces must | |
| // survive or resume fails closed); successful runs self-clean their | |
| // worktree. Keel never deletes branch refs — the orchestrator deletes the | |
| // branch after merging. | |
| retention: "retain-on-failure", | |
| ...(input.install | |
| ? { | |
| setup: { | |
| capabilities: { fs: "workspace-write", shell: true, network: "none" }, | |
| commands: [{ key: "install", command: "npm", args: ["install"] }], | |
| }, | |
| } | |
| : {}), | |
| }); | |
| const codex = ctx.agentSession({ | |
| key: "codex", | |
| provider: "codex", | |
| toolPolicy: input.toolPolicy ?? "unrestricted", | |
| workspace, | |
| reasoning: input.reasoning ?? "high", | |
| providerConfig: { | |
| codex: { | |
| transport: { type: "stdio" }, | |
| ...(input.serviceTier ? { serviceTier: input.serviceTier } : {}), | |
| }, | |
| }, | |
| }); | |
| const runChecks = (label: string) => | |
| ctx.command({ | |
| key: `check-${label}`, | |
| workspace, | |
| cwd: ".", | |
| mode: "argv", | |
| argv: checkArgv, | |
| capabilities: { fs: "workspace-write", shell: true, network: "none" }, | |
| timeoutMs: 900_000, | |
| maxStdoutBytes: 200_000, | |
| maxStderrBytes: 100_000, | |
| failureMode: "return", | |
| }); | |
| // One round = a Codex turn, then check→repair turns until green or budget spent. | |
| const doRound = async (label: string, prompt: string): Promise<RoundResult> => { | |
| let summary = await codex.turn({ key: label, prompt }); | |
| let repairs = 0; | |
| for (; repairs <= maxRepairs; repairs++) { | |
| const check = await runChecks(`${label}-${repairs}`); | |
| if (check.exitCode === 0) { | |
| return { label, checkStatus: "green", repairs, summary }; | |
| } | |
| if (repairs === maxRepairs) break; | |
| summary = await codex.turn({ | |
| key: `${label}-repair-${repairs + 1}`, | |
| prompt: | |
| `The check command (${checkArgv.join(" ")}) failed after your changes. ` + | |
| `Fix the failures, commit the fixes to the branch, and summarize what you changed.\n\n` + | |
| `exit code: ${check.exitCode}\n--- stdout (tail) ---\n${String(check.stdout ?? "").slice(-6000)}\n` + | |
| `--- stderr (tail) ---\n${String(check.stderr ?? "").slice(-4000)}`, | |
| }); | |
| } | |
| return { label, checkStatus: "red", repairs, summary }; | |
| }; | |
| const rounds: RoundResult[] = []; | |
| ctx.phase("Implement"); | |
| rounds.push( | |
| await doRound( | |
| "implement", | |
| `${input.task}\n\n` + | |
| `You are working in a dedicated git worktree on a keel-generated branch. ` + | |
| `Commit your work to this branch as you complete each coherent task ` + | |
| `(imperative messages, spec/code lockstep). Do not merge or push. ` + | |
| `When finished, write your summary to an UNTRACKED file .task-summary.md ` + | |
| `at the worktree root (never commit it): what you built, commits made, ` + | |
| `public API, decisions/judgment calls, spec ambiguities found (cite ` + | |
| `sections) or an explicit "none", and anything deliberately left out. ` + | |
| `Also reply with a 3-line digest.`, | |
| ), | |
| ); | |
| // Park-and-follow-up loop: the run does NOT finish when a round completes. | |
| // Each ctx.signal occurrence is journaled in order, so the loop counter is | |
| // a stable turn-key basis across replay/resume. | |
| let followUps = 0; | |
| let done: { note?: string } | null = null; | |
| while (!done) { | |
| ctx.phase("Parked: awaiting instruction"); | |
| ctx.log( | |
| `round ${rounds.length} ${rounds[rounds.length - 1].checkStatus}; ` + | |
| `signal 'instruction' with {"kind":"follow-up"|"done"}`, | |
| ); | |
| const sig = await ctx.signal<Instruction>("instruction"); | |
| if (sig.kind === "done") { | |
| done = { note: sig.note }; | |
| break; | |
| } | |
| followUps += 1; | |
| ctx.phase(`Follow-up ${followUps}`); | |
| rounds.push( | |
| await doRound( | |
| `followup-${followUps}`, | |
| `Follow-up instructions from the reviewing orchestrator:\n\n${sig.instructions}\n\n` + | |
| `Apply these in the same worktree/branch, commit, refresh ` + | |
| `.task-summary.md (untracked), and reply with a 3-line digest.`, | |
| ), | |
| ); | |
| } | |
| ctx.phase("Finish"); | |
| return ctx.step( | |
| "summary", | |
| Out, | |
| { rounds, note: done?.note ?? null }, | |
| (x: { rounds: RoundResult[]; note: string | null }) => ({ | |
| rounds: x.rounds.map((r) => ({ | |
| label: r.label, | |
| checkStatus: r.checkStatus, | |
| repairs: r.repairs, | |
| })), | |
| finalStatus: | |
| x.rounds.length > 0 ? x.rounds[x.rounds.length - 1].checkStatus : "red", | |
| closeNote: x.note, | |
| }), | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment