It started when I was playing with Claude Code workflows and trying to make them more programmable. Like, what if a workflow could be defined in my own files, a foreach over a folder of prompts? Keep your agent running after it gives a result, keep the context. Fork, compact?
Nope, not possible.
Okay, let’s build it, how hard can it be?
Because I already have workflows for different tasks, I just run them by hand. Write a design doc. Tell an agent to review it, then review again. Is it good? Take a look myself. Review it with subagents. Then build, review in a cycle, write test cases, run e2e tests, and so on.
All of this is scriptable. Certain points need real understanding: giving directions, making sure the agent got it. But between those points the agent is capable of doing things correctly if it is prompted properly, and if its work gets verified independently. And most of the time I am just running the same skills and commands, in the same order, again and again.
So I started building agentswf. A workflow is a TypeScript file, awf run runs it, and the agents are the real harnesses (the CLI apps like Claude Code, codex, pi or cursor, not just the model behind them), running in terminal panes you can watch and type into.
Here is roughly what workflow looks like (a sketch, error handling left out):
// a different model checks the work
const planner = await workflow.agents.open({ key: "planner", runtime: "claude" });
const reviewer = await workflow.agents.open({ key: "reviewer", runtime: "codex" });
const doc = await workflow.stage("design-doc", { result: DOC }, () =>
planner.run({ prompt: `Write the design doc for ${ticket}`, schema: DOC }),
);
await workflow.stage("doc-review", { result: REVIEWED }, async () => {
await reviewUntilReady(reviewer, planner, doc); // plain for loop
if (doc.openQuestions.length) {
workflow.stop("questions for me"); // I answer, then --continue
}
});
const builder = await workflow.agents.open({
key: "builder",
runtime: "claude",
sandbox: { srt: {} },
});
await workflow.stage("build", { result: WORK }, async () => {
const work = await builder.run({ prompt: "Implement the doc", schema: WORK });
await builder.compact({ prompt: "Keep what was built and every deviation from the doc." });
return work;
});
// the reviewer reads the change once, then forks into lenses
await workflow.stage("review", { result: REVIEWED }, async () => {
await reviewer.run({ prompt: "Read the change, don't review yet", schema: READ });
const findings = await workflow.parallel(LENSES, async (lens) => {
const fork = await reviewer.fork({ key: lens.key }); // starts with everything the reviewer knows
return fork.run({ prompt: lens.prompt, schema: FINDINGS });
});
return fixUntilClean(builder, findings); // plain for loop again
});
await workflow.stage("e2e", { result: EVIDENCE }, () =>
builder.run({ prompt: "Write tests, run e2e", schema: EVIDENCE }),
);
the real harness, on my subscription
Most agent frameworks call a model API. I wanted the actual harness, because Claude Code is tuned to work with its model and already has everything I use: skills, MCPs, compaction, my settings (and memories). So agentswf drives the CLIs as if I were typing into them, through herdr, a terminal multiplexer for coding agents that lets a script type into agent panes and read what they are doing. Headless claude -p would be simpler, but it is billed against usage credits, not the subscription, and it is not the session I actually work in.
And debugging is so much easier when you can watch what each agent is doing in herdr. When one goes the wrong way I can just jump in and type more instructions, like with any other session.
And it runs on my Claude subscription. No API key, no credits. One heavy user measured a fully used $200 Max plan at about $3,650 a month at API prices. Workflows burn tokens, multiple agents reviewing the same thing again and again, and the subscription lets one person experiment the way big labs do, without worrying about the bill. Will subscriptions be subsidized forever? I don’t know, but by then other models will catch up, and the workflow is mine either way. Each agent picks its own harness and model, so a codex reviewer can check a Claude implementer. I am not a huge believer in local models, because the hardware is expensive, but a trusted provider running an open source model, why not.
Is that allowed? As far as I can tell, yes. It is the official Claude Code, logged in the normal way, used for my own work, and what Anthropic does not allow is third-party apps offering Claude login or routing requests through your plan.
workflows as code, in TypeScript
Claude Code’s dynamic workflows are JavaScript scripts, and that is where I started. Instead of begging an agent to run subagents of a specific type, I define it all in code: which agents, which models, how many rounds, how much budget, and how to combine their reasoning. Control flow is yours: for, if, Promise.all, whatever.
In agentswf the workflows are TypeScript, because most of the time a coding agent writes them, and a compiler gives it something to check its work against. The agent’s answers are typed too, the schema you pass to a turn is the type you get back. If workflows are going to get complicated, I want a strong type system under them.
Unit tests are part of the same quick feedback loop. awf test runs the workflow with scripted answers instead of real agents, including the bad ones: an agent that goes silent, hangs or fails. It takes seconds and no tokens, so the agent writing the workflow can compile, test and fix before anything real runs.
answers, not prose
A workflow has to make decisions based on what an agent said. Is the review clean? How many must-fix findings? You can’t branch on a paragraph. So the agent answers by running wf result with JSON, and it is validated before the workflow sees it. A turn ends with an answer or with a reason why not, never with a half-parsed message.
With every prompt awf adds a short note on how to answer, and when the agent is done it runs a tiny CLI:
wf result c42 '{"clean": false, "mustFix": 2}'
If the JSON doesn’t match the schema it is refused, and the agent fixes it in the same turn. Anything that can run a shell command can answer, so it works with any harness and any model.
agents that keep their context
Real work is not one prompt. The agent that wrote the design doc should be the one fixing it after review, without reading everything again. So when you talk to the same agent again it continues its own session, and each turn can ask for a different kind of answer: a plan, then a summary of the patch, then yes or no.
Forking is for when you want to try two directions from the same point, or look at the same thing from different angles. Each fork starts on a copy of the parent’s session, with everything it already knows. The copy is made by the harness itself, with no model call, so where the harness allows it the provider’s prompt cache still hits and the fork doesn’t pay for the parent’s whole context again. This made a bigger difference than I expected. In code review, lenses forked from one agent that already read the change almost never made things up, 1 wrong claim in 45. The same lenses started cold got 6 of 33 wrong. Giving the cold ones a summary didn’t help, a summary isn’t the context.
Same with compaction. Don’t wait until the context overflows, compact between tasks, with the right focus. The workflow tells the harness to compact, the same as /compact, so in my sketch the builder keeps what it built and every deviation from the doc before the review starts.
sandboxes
I like pi, Mario Zechner’s minimal coding agent, it is a cool idea. But it runs in “full YOLO mode”, as he puts it, no permission prompts at all, and his own advice is to run it in a container if that makes you nervous. And integrating agents with sandboxes is a real pain, it is the last thing I want to do when I am working on a hard problem. Claude Code has auto mode, and it might work, but it still rejects things, and it can still make mistakes. The safer route is to make sure the agent can’t see what it shouldn’t see. If it wants to hack around that, it has to be a lot more determined, and it is unlikely to do it by accident. That is especially useful with smaller models, which get confused more easily.
In agentswf it is a few lines: docker or Anthropic’s sandbox runtime (srt), shared or private, what the agent can write and which domains it can reach.
// one docker container, shared by everyone who opens it
const box = await workflow.sandboxes.open({
key: "team",
write: ["."],
network: ["github.com"],
docker: {},
});
const builder = await workflow.agents.open({ key: "builder", runtime: "claude", sandbox: box });
// or a private srt sandbox, just for this agent
const tester = await workflow.agents.open({
key: "tester",
runtime: "pi",
sandbox: { srt: {}, write: ["./tests"] },
});
Once the agent is in a sandbox, agentswf turns the permission prompts off, because the sandbox is the one enforcing the limits.
Sandboxes are also what make testing honest. A workflow under test can’t peek at the answer, look at git history, or reach somewhere it shouldn’t.
skills
Skills are useful, but honestly I am a bit afraid to install new ones. A skill can change the default behavior of the model, multiple skills collapse into each other, or the agent tries something complicated when a simpler option was right there. And you don’t know if one really works, or if it is safe. Everybody shares skills, almost nobody measures them.
So in agentswf you name the skills each agent gets, and the rest of what you have installed stays out. It makes more sense to enable a skill for a specific step of the workflow, not for everything, and then you can compare with and without. When I did that for code review, the popular review skills I tried actually found fewer issues than a few focused reviewers. My guess is they are written to avoid noise, so they only report what they are very sure about, and a lot of real issues get dropped along the way.
const reviewer = await workflow.agents.open({
key: "reviewer",
runtime: "claude",
skills: [
{ path: "./skills/review" }, // a local skill folder
{ repo: "someone/skills", skill: "security-review" }, // or one from GitHub
],
});
the lab
Once a workflow is code, you can run variants against each other: a plain agent, an agent with a skill, a whole workflow. Otherwise every change to a prompt or a workflow is a guess. It burns tokens, but you probably have unused limits on your subscription, why not use them.
The first real use case is code review, and it grew into a whole project. I ran it on internal merge requests at work. The answer key is what came up in review or got fixed later, and LLMs do the judging. The full set has 33 MRs, but most experiments ran on 8 of them to keep it fast and cheap, so the numbers are early. But it showed me that a single agent is not very effective, even with a review skill (Opus on its own does surprisingly well, but it is not cheap). Three identical reviewers mostly found the same things as one. Six reviewers each looking for different things found more than twice as much. That is a post of its own, coming next.
The lab can also hill-climb on its own. The test cases are the evals, and an LLM verifier scores every run against the answer key, including what the review missed and why. An agent reads that feedback and proposes one change to the workflow. The lab runs it against the current version on the same cases, in a sandbox, keeps it if it scores better and throws it away if not, then tries again until the budget runs out.
A few cases are held out, the proposer never sees them, and the final version is checked on them once, so it can’t just learn the test cases by heart. The first live loop worked end to end, and its first try got discarded, it proposed something too generic.
Where I want this to go: collect test cases as you do the work, so your solved problems become your evals. Evals are boring, but not if they collect themselves. Then use the smart model to write a repeatable workflow, and a cheap model to run it. It already works for review: the same lenses on Haiku plus a codex reader matched the Sonnet version for about a seventh of the cost.
durable runs
Workflows run for a long time, and long things fail in the middle. So stages are durable: when you continue a run, finished stages already have their answers, and it picks up from where it stopped instead of paying for everything again. It is also how a human steps in for now: the workflow stops with its questions, I answer, and --continue picks it up.
If you know Temporal or Inngest, it is a similar idea, closest to Inngest’s steps: a finished step keeps its result and doesn’t run again. But there is no server to run, it works on your machine with little overhead.
Everything is stored as plain JSON and markdown files in the project, so it is portable and you (or your agent) can read it without any special tools.
From here the next step is loops. Workflows that wake up on a schedule or on an event, check something, do their part and go back to sleep, with some kind of workflow daemon keeping them running on your machine.
messaging
Agents can message each other if the workflow allows it, and every message is logged with the run. This is the part that needs the most experimentation. What is the right way for agents to collaborate? Sharing a file or sending a message is kind of the same thing, it is all part of the communication strategy. And people have already tried most of the options, just look at how companies are organized. A manager who hands out work and collects the results. A tech lead with a few engineers who talk to each other directly. Or a trial, with a judge and a prosecutor. Each one is a different answer to who talks to whom and who decides. The only way to find out what works for agents is to try them and measure.
is it ready?
Lots of things are missing, a lot is in flight and being experimented on, the API may change, or new behavior turns out to be needed. And it is not clear it needs all of them. A proper human approval in the middle of a run isn’t there yet, for now it is stop and --continue.
What I’d like next is a smart model that steps in when a workflow gets stuck and fixes it, and events from the agent’s state, like an invisible spy watching. awf powered by awf, that can be quite cool. And then plug it into a bigger system of loops, with events flowing between workflows and in from the outside. And integration with OpenClaw or Hermes, so those agents can run their repeatable work as workflows and be more token efficient. And a coordinator, an agent that takes requests, forwards each one to the right workflow and coordinates between them.
It works with Claude Code, codex, cursor and pi so far, and any other harness can be added. So whatever agent you use, try agentswf and script the workflow you already run by hand. I want feedback, ideas, comments. And if you are building something similar, running evals on agent workflows, or have ideas on how agents should collaborate, reach out, maybe we can work on it together.