A macOS app for running agent workflows as a graph.
zopf allows you to connect several claude, codex or deepseek harness sessions into a graph and run them together instead of one terminal session at a time. You can also span this across multiple directories and invoke skills and other context on the side.
It runs claude code in the non-interactive mode using claude -p, but you can also use codex or deepseek harness
instead. There's also a CLI which allows you to run zopf workflows headlessly.
- Live output with cost and elapsed time
- Take a session over in your terminal
- Menu bar as an overview of your runs and workspace
- Gates to pause a run for your approval
- Connectors to communicate with external components
You need macOS on Apple Silicon and one of these installed and signed in:
| CLI | provider: |
What zopf runs |
|---|---|---|
| Claude Code | claude |
claude -p, streaming JSON |
| Codex | codex |
codex exec --json |
| DeepSeek Harness | dsh |
dsh --profile headless |
Note
While zopf supports claude code, codex and dsh so far, I've only extensively tested claude code as it is my daily driver.
claude and codex installs can just follow the official instructions. dsh needs one extra step: install it
globally, because zopf looks for a dsh binary on your login PATH. After
that, follow their guide to add a provider,
since dsh ships no model of its own. Note that dsh itself is still a 0.1.x developer preview.
Download zopf-<version>.dmg from the latest release and drag it to Applications. On first
launch it creates ~/.zopf as your workspace.
curl -fsSL https://raw.githubusercontent.com/justdeko/zopf/main/install.sh | shThat unpacks the newest release into ~/.local/opt, links ~/.local/bin/zopf, and adds that directory to your shell rc
if it isn't on PATH already.
The CLI does need a JDK 21 or newer on your PATH, you can install one e.g. with homebrew:
brew install --cask temuringit clone https://github.com/justdeko/zopf && cd zopf
# basic run
./gradlew :desktopApp:run
# build the .app and open it from finder
./gradlew :desktopApp:runMacApp
# cli/build/install/zopf-cli/bin/zopf
./gradlew :cli:installDistA workflow is a YAML file. As an example, this one runs the test suite and only calls an agent if it fails:
name: verify
repos:
- id: app
path: ~/dev/app
defaults:
repo: app
nodes:
- id: tests
type: shell
title: Run the suite
command: npm test
- id: diagnose
type: agent
title: Fix what broke
permissionMode: acceptEdits
prompt: |
`npm test` just failed. Its output:
${tests.result}
Find the cause and fix it, then re-run the suite and report what is green.
edges:
- from: tests
to: diagnose
on: failureSome things that are crucial to understanding workflow nodes:
- data along edges is implicit, you can embed it in the next node execution using literals:
${tests.result} - edges can carry a condition:
on: failurein the example, orwhen: true/when: falsefor branch nodes - an
on: failureedge does not fail the run (node still shows as failed) - a node with several incoming edges waits for all of them to finish
- generally, follow the schema reference
The editor reads and writes to workflow files, so you can hand-edit it or draw it on the canvas. You can also describe what it should do and let an agent draft the graph for you. Or you can invoke the claude skills to construct one. To add them:
/plugin marketplace add justdeko/zopf
/plugin install zopf@zopf
- Fan one migration out over a dozen repos in parallel and collect a diff from each
- Review with an expensive model, then apply the fixes with a cheap one
- Summarize a folder of documents in parallel, then have a second agent write the digest
- Ask for your approval or your input in the middle of an otherwise unattended run
| Type | What it does |
|---|---|
agent |
A headless agent session. Set provider: codex or provider: dsh to switch CLIs (or change your default) |
shell |
A command that runs in your login shell in the repo you point it at |
connector |
A script that talks to something outside |
gate |
Stops and waits for you to approve or reject |
input |
Asks you a question and passes the answer on as ${ask.result} |
branch |
Picks one outgoing based on a condition, e.g.: "${build.exitCode} == 0". |
Steps read previous output with ${step.result}. There's no expression language on top of it.
A workspace is a .zopf/ folder, similar to git with .git. Use one anywhere, otherwise ~/.zopf is the default for
everything else.
<repo>/.zopf/ or ~/.zopf/ workflows/, connectors/, optional zopf.yaml
~/Library/Application Support/zopf/ settings.json, runs/ (every run, as replayable JSONL)
~/Library/Logs/zopf/zopf.log what to send with a bug report
zopf.yaml is optional. It names the workspace and sets defaults for every workflow in it, using the same keys as the
workflow's defaults:
name: Payments
defaults:
model: sonnet
repo: selfThe run archive grows, so a chatty build step can run quite large. To cap it, go to Settings → History, or do
zopf prune --keep 50.
zopf run ship-feature --on-gate approve --answer env=staging
# only prints order of workflow execution
zopf run ship-feature --dry-run
# list workflows in a given folder
zopf list
# check workflow validity
zopf validate
# show past runs
zopf runs --last 5
# resume the last run from where it stopped
zopf run --resume lastExit codes:
0OK1a step failed and was uncaught2stopped3never started
zopf run validates the workflow before it starts anything, so an error in the file means exit 3 and no node runs.
Two things are a bit different in the cli vs. the app:
- no gate, the run just stops there unless you specify
--on-gate approve - inline approval is off, so a stricter permission mode can block the run
zopf runs on your laptop under your own account and logins. Every step is a subprocess with your file permissions.
There are no external tools or connections other than what you build yourself using connectors, scripts, and so on. The
app checks this GitHub page once a week to determine whether a newer release exists. To silence update checks:
ZOPF_NO_UPDATE_CHECK=1 or DO_NOT_TRACK=1.
Point workflows at locations you'd already be willing to run from, and only ingest agent output into shell execution if you're confident of its contents.
zopf is an independent personal project. It is not affiliated with, sponsored by or endorsed by Anthropic, OpenAI, DeepSeek, Apple or GitHub. Claude and Claude Code are trademarks of Anthropic PBC; Codex is a trademark of OpenAI; DeepSeek is a trademark of Hangzhou DeepSeek Artificial Intelligence Co., Ltd.; macOS and Finder are trademarks of Apple Inc. They're named here only to say which tools zopf uses.


