This is the project-level Claude Code config for oh-my-graph itself (public, committed — no personal or private settings live here).
oh-my-graph is a graph-native multi-agent orchestrator: it runs each node of
a YAML-defined DAG as a real claude -p subprocess on the user's own
logged-in Claude subscription (never the metered Anthropic API). Nodes run
with session persistence on, so every node is also an ordinary claude session
in ~/.claude/projects that any external tool can read.
Since ADR 0025 a run may instead select --runtime codex, which spawns
codex exec on the user's own Codex login by the same rule — one runtime for
the whole run, the provider's own CLI, that provider's saved login. Claude is
the default and the path every unqualified statement in this file describes.
DESIGN.md is the spec. Read it before touching the scheduler, the graph
schema, the NodeRunner interface, or handoff. If code and DESIGN.md
disagree, treat that as a bug in one of them, and fix both together — don't
let them drift apart.
- Subscription-auth env scrub. Every child process's environment must have
ANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKEN,OPENAI_API_KEYandCODEX_API_KEYdeleted, via the sharedinternal/childenv.Scrub— used by ALL spawners (a model node, asuccess_check.verifycommand, the git commands behind a node'sworktree:, and the browser launch of theserveURL). This is what keeps the tool on subscription billing instead of silently falling back to metered API billing. It is unit-tested ininternal/childenv/childenv_test.goand at each call site (internal/runner/claude_test.go,internal/verify/shell_test.go,internal/worktree/git_test.go,internal/browser/exec_test.go) — don't touch env construction without keeping those tests meaningful. There is ONE list and NO runtime branch: a Claude node drops the OpenAI switches and a Codex node drops the Anthropic ones. Splitting it in two — "each runtime only needs its own" — is the tempting optimisation, and the half that then falls behind a new provider variable bills silently, with nothing failing to say so. - The four exec seams. Exactly four objects may spawn a process:
runner.CLIRunner(a node's model CLI subprocess, claude or codex),verify.ShellVerifier(a node's evidence command),worktree.GitManager(thegit worktreecommands behind a node'sworktree:) andbrowser.ExecOpener(theopen/xdg-openlaunch of theserveURL) — seedocs/adr/0002-verification-is-a-second-exec-seam.md,docs/adr/0005-worktree-provisioning-is-a-third-exec-seam.mdanddocs/adr/0006-browser-open-is-a-fourth-exec-seam.md. Everything else (scheduler, CLI, handoff, ledger, coordinator) depends on theNodeRunner,Verifier,worktree.Providerandbrowser.Openerinterfaces only, so the whole engine is testable via the scriptedFakeRunner/FakeVerifier/FakeManager/FakeOpenerwith zero real spawns. A fifth spawner needs its own ADR. - Artifact handoff is the default.
handoff: artifactpersists a node's result to~/.oh-my-graph/runs/<run-id>/<node-id>.out(base overridable viaOMG_HOME);handoff: session(resuming--resume <session_id>) is opt-in and only valid with exactly one session-parent. - Never a provider SDK. Never
--bare. Never--no-session-persistence. A node runs as the provider's own CLI subprocess —claudeorcodex, one runtime for the whole run (ADR 0025) — on whatever login that CLI has saved, with session persistence on (so every node stays observable as an ordinary session transcript). A second runtime widens which CLI, never the rule: no direct model API, and no flag that detaches a node from its session. Be exact about what the billing guarantee IS: oh-my-graph guarantees the env scrub above and never passing--bare, so nothing IT does switches a CLI off its saved login. It cannot guarantee that login is OAuth —codex login --api-keywrites a key into~/.codex/auth.json, which no scrub touches, and the same holds for any credential a CLI persists on disk. SECURITY.md andinternal/childenv/childenv.gostate it in that narrower form ("can make the selected CLI ignore its saved login"); this file must not state it in a wider one.
See SECURITY.md for the full ToS/security stance and CONTRIBUTING.md for how these invariants are enforced in review.
make build # go build -o bin/oh-my-graph ./cmd/oh-my-graph
make test # go test ./... -race -count=1 — FakeRunner only, no real claude
make vet # go vet ./...
make fmt-check # gofmt -l . (CI gate)
make smoke # MANUAL ONLY: real claude, a few cents, never in CIAll engine logic (scheduler, DAG validation, handoff, retry, halt-on-fail) is
covered by internal/runner.FakeRunner fixtures. If you add or change
scheduling/runtime behavior, write the test against FakeRunner, not a real
claude spawn.
cmd/oh-my-graph/ CLI entrypoint and flag parsing
internal/graph/ Graph/Node value objects, YAML load, DAG validation
internal/schedule/ ready-set scheduler (the engine's core)
internal/runner/ NodeRunner interface, CLIRunner + its claude/codex protocols (argv, session, output), FakeRunner
internal/verify/ Verifier interface, ShellVerifier, RefusingVerifier, FakeVerifier
internal/worktree/ worktree Provider seam: GitManager (third exec seam), RefusingProvider, FakeManager
internal/browser/ browser Opener seam: ExecOpener (fourth exec seam), RefusingOpener, FakeOpener
internal/childenv/ the shared child-env scrub policy (used by all four spawners)
internal/fence/ the shared data fence: a per-call crypto/rand nonce in BOTH markers of any quote of untrusted text into a prompt, plus the head+tail bound on it
internal/invariants/ test-only: asserts exactly four spawner objects — only their seams' files import os/exec
internal/coordinator/ auto mode: goal → planner call → validated graph + ToolPolicies; agent mapping — scanned from ~/.claude/agents only, with the matched definition staged so a mapped node keeps ceiling layer 1 (ADR 0022) — and skill activation (ADR 0017, superseding ADR 0012's plan-time inlining); the goal loop
internal/handoff/ {{inputs}}/{{artifacts}} interpolation, artifact/session handoff, the advisory lint sweeps
internal/runstate/ state.json snapshot — atomic write, resume load, and the run lock (an flock a leg holds; ProbeLock reads it — ADR 0015)
internal/runfeed/ events.jsonl append-only event stream (consumer contract, docs/RUN-FEED.md)
internal/runstatus/ the ONE settled/in-flight/abandoned rule — open leg AND held lock (ADR 0015); runs list, the dashboard card, ResolveRun, the single-run view and watch all go through it
internal/serve/ `serve`: 127.0.0.1-only dashboard + per-run live view, a run-feed consumer
internal/ledger/ RunLedger (per-node + total cost/verdict summary)
internal/gate/ gate Decision + PauseController/RecordedController (human pause/approve)
graphs/ shipped example graphs + graphs/fragments/, embedded with //go:embed *.yaml fragments/*.yaml
docs/adr/ architecture decision records