Skip to content

Instantly share code, notes, and snippets.

@kcosr
Created July 4, 2026 18:20
Show Gist options
  • Select an option

  • Save kcosr/c1fe24b6732707dcf7e4a52e5f3d9c40 to your computer and use it in GitHub Desktop.

Select an option

Save kcosr/c1fe24b6732707dcf7e4a52e5f3d9c40 to your computer and use it in GitHub Desktop.
formwork-codex-task workflow
// 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