Authoring Processes
A babysitter process is the definition of a semi-deterministic loop. Writing one is how you teach Pinard to do something new — it is the point at which Pinard becomes an engine you build on rather than a fixed tool.
Where a process lives
A process is a JavaScript module, versioned with the code it operates on:
<repo>/pinard/<process>/process.js
At launch the worker resolves the process file (exposed to the loop as
BABYSITTER_PROCESS_PATH); set PINARD_PROCESS_FILE to point at another file when
iterating locally. Spawn a worker on a process with --process <name>.
Anatomy
A process has two parts: task definitions and the process function that sequences them.
Task definitions
Deterministic process function
Typed task output
Branch on typed result
Breakpoint gate
Terminal result
Journaled checkpoints · resume at the next incomplete task- TTask — one bounded unit of work with typed inputs and outputs.
- JSControl flow — plain JavaScript determines sequence and branches.
- GBreakpoint — pauses before a risky transition and records the verdict.
- JJournal — completed tasks are replayed, not repeated, after resume.
Tasks — the steps
Define each step with defineTask. An agent task (kind: 'agent') is a bounded
LLM turn with a typed output contract:
import { defineTask } from '@a5c-ai/babysitter-sdk';
const fetchIssue = defineTask('fetch-issue', (args) => ({
kind: 'agent',
title: 'Fetch issue content',
agent: {
name: 'issue-fetcher',
prompt: {
role: 'A developer fetching issue details',
task: `Use read_issue to fetch issue #${args.issueId} on "${args.project}".`,
},
// The model MUST return this shape — the loop can rely on it downstream.
outputSchema: {
type: 'object',
properties: {
title: { type: 'string' },
description: { type: 'string' },
},
required: ['title', 'description'],
},
},
}));
The outputSchema is what keeps a non-deterministic step usable: the model may
write freely, but it must return a predictable structure the rest of the loop
consumes.
The process function — the control flow
The exported process(inputs, ctx) function is deterministic code. It decides
the order, branches, and stopping condition; it only reaches into the model by
running a task:
export async function process(inputs = {}, ctx) {
const { project, parcelle, runId } = inputs;
// Run an agent step and get its typed result.
const issue = await ctx.task(fetchIssue, { issueId: inputs.issueId, project });
// Deterministic branching on the model's structured output.
const plan = await ctx.task(analyzeWork, { issue, project });
await ctx.task(implement, { plan: plan.plan, affectedFiles: plan.affectedFiles });
let tests = await ctx.task(runTests, {});
if (!tests.passed) {
// A gate: pause for a verdict (human or conductor) before continuing.
// The verdict object carries { approved, option, response }.
const verdict = await ctx.breakpoint({
question: `Tests still failing after a fix attempt — retry, skip, or abort?`,
options: ['Retry', 'Skip tests', 'Abort'],
});
if (verdict.option?.toLowerCase().includes('abort')) return { status: 'aborted' };
if (verdict.option?.toLowerCase().includes('skip')) {
// continue without re-running tests
}
}
const mr = await ctx.task(openMR, { project, runId });
return { mrIid: mr.mrIid, status: 'opened' };
}
The primitives you compose:
| Primitive | Purpose |
|---|---|
ctx.task(def, args) | Run a step (agent turn or deterministic task); returns its typed result. Journaled. |
ctx.breakpoint({ question, options? }) | A gate — pause the loop for a verdict/decision. Returns { approved, option, response }. approved is true for any non-abort selection; false only for Abort. When options are provided the operator chooses one and its verbatim text is in option — use gate.option.includes('skip') to branch. Set PINARD_BREAKPOINT_AUTO_APPROVE=1 to auto-approve gates for fully unattended runs. Journaled. |
plain JS (if, for, try) | Deterministic control flow — the “semi-deterministic” half. |
return | Terminal condition — the loop’s honest outcome. |
Journaling & resume — for free
Every ctx.task and ctx.breakpoint is recorded in the run journal under
<runs-dir>/<run-id>/. You do not write resume logic: because the run id is
stable, re-invoking the process replays the journal, skips completed steps, and
continues from the next one. Design steps to be idempotent where a re-run could
repeat side effects (e.g. check “does the MR already exist?” before opening one).
Design guidelines
- Push judgment into small steps. Each agent task should do one bounded thing with a clear output contract — not “here’s everything, go.”
- Keep control flow in code. Branch on structured outputs, not on re-reading the model’s prose.
- Gate the risky transitions. Use
ctx.breakpointwhere a wrong step is expensive or irreversible. - Make steps idempotent. Assume any step may run twice after a resume.
- Emit knowledge. Save decisions and discoveries to memory so the next run of this process starts smarter.
Reference process
The built-in SWE process (processes/swe.js) is a complete,
production example: fetch → analyze → implement → test → open MR → tend pipelines.
Read it alongside this page as a template.