Branches, loops and parallel work
Express the process in ordinary code and bound retries by a clear policy.
Code owns the process: an agent proposes work, while your workflow decides what happens next. You do not need a separate graph definition to run a branch or loop.
Parallelize independent checks
This complete workflow simulates two deterministic reviewers. Save it at
laufwerk/workflows/check-draft/workflow.ts and run with a request ID and draft.
It uses no model or credentials.
import { Activity, Workflow } from "@effect/workflow";
import { Effect, Schema } from "effect";
const Check = Schema.Struct({ name: Schema.String, passed: Schema.Boolean });
export const workflow = Workflow.make({
name: "check-draft",
payload: { requestId: Schema.String, draft: Schema.String },
success: Schema.Struct({ accepted: Schema.Boolean, checks: Schema.Array(Check) }),
idempotencyKey: input => input.requestId,
});
export const layer = workflow.toLayer(input => Effect.gen(function* () {
const rules = [
{ name: "has-content", passed: input.draft.trim().length > 0 },
{ name: "length", passed: input.draft.length <= 500 },
];
const checks = yield* Effect.all(rules.map(rule => Activity.make({
name: rule.name, success: Check, execute: Effect.succeed(rule),
})), { concurrency: 2 });
return { accepted: checks.every(check => check.passed), checks };
}));"Hello" passes both checks; an empty draft fails has-content. The activity
results are recorded separately. These rules are deliberately simple so you can
see the orchestration; they do not measure writing quality.
Replace the activities with independent Session.run calls when you need agent
review. Give every session a stable key. Shared Docker read-only workspaces suit
parallel review; independent writable copies need separate runs.
Bound a revision loop
Draft → review → accepted? ── yes → return accepted result
│
no → revision available? ── yes → revise → review
│
no → return unresolved resultSet a maximum number of drafts before starting. Derive durable keys from the round,
such as draft-0 and review-0. Keep feedback linked to the candidate it assessed.
A negative review is ordinary data; an exception means the review did not finish.
Do not catch every error and translate it into “please try again.”
The complete workflow implements a two-draft limit and
returns revision-limit if the second draft is still rejected. That policy differs
from the optional issue-to-fix starter, which can continue with objections after
its two-pass limit. Your code chooses the policy.
Execution order and pictures
Effect.all(..., { concurrency: 2 }) controls parallelism. A
Visual annotation only describes the structure for Studio;
it does not schedule work or enforce the declared concurrency.
Reference: Activities · Visual annotations
← Structured data and real checks · Next: Human input and review →