factory
factory

Writing workflows

.md

The shape of defineWorkflow and the ctx you get inside run.

A workflow is one file that default-exports defineWorkflow. It declares its input, and its body is a plain async function.

import { defineWorkflow, Schema } from "@frebreco/factory";
export default defineWorkflow("my-workflow", {
input: Schema.Struct({ topic: Schema.String }),
output: Schema.Struct({ prUrl: Schema.NullOr(Schema.String) }),
agent: { model: "opencode-go/glm-5.3-flash" },
run: async (ctx, input) => {
/* ... */
},
});
Field What it is
id The name you start it by — in the UI’s picker and in factory start <id>
input A schema. It generates the UI’s form and validates --input before the run starts
output Optional. What run resolves to, recorded in the run’s history
agent Optional defaults for every agent step — mainly model
run Your workflow: async (ctx, input) => output

The ctx you get

Six things, and that’s the whole surface.

Call What it does
ctx.agent(name, prompt, opts?) Runs one agent step in the working tree. Each call is a fresh session with no memory of the last one.
ctx.exec(["bun", "test"]) Runs a command. Returns { exitCode, stdout, stderr }. It never throws, so a failure is just an if.
ctx.writeBack({ branch, commitMessage, … }) Branches, commits, pushes, and opens the PR. Real git and gh, run by factory, not the agent.
ctx.assert(name, () => …) Records a named check in the run’s history. Returns the result rather than throwing.
ctx.log(name, data) Puts anything you want into the run’s timeline.
ctx.dir The working tree’s path. Factory cloned it for this run; the agent is already in it.

Structured output from an agent step

Pass a schema and you get a typed value back, not a string to parse:

const meta = await ctx.agent("pr-metadata", "Summarise the change as JSON.", {
output: Schema.Struct({ title: Schema.String, body: Schema.String }),
});
meta.output; // { title, body } | undefined
meta.finalText; // the raw text, always

Registering it

A workflow exists because it’s imported into your config. There’s no directory scan and no magic: the array is the registry.

import hello from "./workflows/hello";
import fixIssue from "./workflows/fix-issue";
export default defineConfig({
workflows: [hello, fixIssue],
/* ... */
});

Next

  • Configuration — the rest of factory.config.ts.
  • Schedules — dispatch a workflow automatically, on a cron.