{"id":47741268,"url":"https://github.com/flowglad/onton","last_synced_at":"2026-06-04T00:01:42.420Z","repository":{"id":344518062,"uuid":"1182082238","full_name":"flowglad/onton","owner":"flowglad","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-02T20:00:09.000Z","size":16417,"stargazers_count":9,"open_issues_count":6,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-02T20:09:59.922Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://onton.vercel.app","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flowglad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-15T02:31:42.000Z","updated_at":"2026-06-02T20:00:20.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/flowglad/onton","commit_stats":null,"previous_names":["flowglad/onton"],"tags_count":71,"template":false,"template_full_name":null,"purl":"pkg:github/flowglad/onton","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowglad%2Fonton","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowglad%2Fonton/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowglad%2Fonton/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowglad%2Fonton/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flowglad","download_url":"https://codeload.github.com/flowglad/onton/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flowglad%2Fonton/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33884734,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-03T02:00:06.370Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-02T23:53:20.926Z","updated_at":"2026-06-04T00:01:42.413Z","avatar_url":"https://github.com/flowglad.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# onton\n\n![onton](onton.png)\n\nA methodology and orchestrator for breaking complex codebase changes into\nparallel, spec-verified patches executed by AI agents.\n\n## The gameplan approach\n\nLarge changes are hard to review, easy to get wrong, and risky to merge. The\ngameplan approach decomposes them into small, ordered patches — each with a\nformal specification (written in\n[Pantagruel](https://github.com/subsetpark/pantagruel)) that states what must\nbe true after the patch is applied. A dependency graph identifies which patches\ncan run in parallel. An orchestrator spawns AI agents in isolated git worktrees\nand manages the full lifecycle: PR creation, code review, CI, rebasing, and\nmerge.\n\nThe approach has three layers:\n\n| Layer | What | Where |\n|-------|------|-------|\n| **Scoping** | Break a large project into sequenced gameplans (milestones) | `skills/write-workstream/` |\n| **Planning** | Structure a change into patches with specs, tests, and a dependency graph | `skills/write-gameplan/` |\n| **Execution** | Orchestrate parallel agents, poll GitHub, react to events | `onton` (this binary) |\n\n### Skills\n\nThe `skills/` directory contains Claude Code skills for the planning layers:\n\n- **write-workstream** — Define a large project as a sequence of gameplans\n  (milestones), each a safe stopping point. Guided discovery process: vision,\n  challenges, milestones, dependencies.\n\n- **write-gameplan** — Create a structured JSON gameplan with typed\n  sections, patch classifications (INFRA/GATED/BEHAVIOR), formal specs,\n  test maps, and a dependency graph. Designed so it's 5-10x easier to review\n  the gameplan than the code it produces.\n\nTo make these skills available to Claude Code from any project on your\nmachine, run:\n\n```sh\n./scripts/install-hooks.sh\n```\n\nThis symlinks each skill into `~/.claude/skills/` and installs a git\n`post-checkout` hook that keeps the symlinks pointed at the current checkout\nafter branch switches. Running `./scripts/sync-skills.sh` directly has the\nsame effect without installing the hook.\n\n## Onton: the orchestrator\n\nOnton parses a structured gameplan, builds a dependency graph, and spawns\nconcurrent Claude Code agents in git worktrees — one per patch. It polls GitHub\nfor PR status, detects merges, triggers rebases, and reacts to CI failures and\nreview comments. A terminal UI shows live status with full markdown-rendered\nagent transcripts.\n\nIts control path is replay-safe: a pure controller derives the same follow-up\neffects and the same patch-agent messages from the same durable snapshot on any\ntick. Patch-agent delivery is effectively-once: messages have stable logical\nIDs, are persisted in an outbox, are acknowledged durably before work begins,\nand can be resumed after crashes without reapplying the queue-consuming state\ntransition that originally accepted them.\n\n## Install\n\nPick **one** of the following methods:\n\n### Option A: Homebrew (macOS)\n\n```sh\nbrew tap flowglad/onton https://github.com/flowglad/onton\nbrew install onton\n```\n\n### Option B: GitHub Releases\n\nDownload a prebuilt binary from\n[Releases](https://github.com/flowglad/onton/releases) (macOS ARM64 and\nx86_64).\n\n### Option C: From source\n\nOnly needed if you want to modify onton or are on a platform without prebuilt\nbinaries. Requires OCaml 5.4.1, dune 3.21, and opam:\n\n```sh\ngit clone https://github.com/flowglad/onton.git\ncd onton\nopam switch create . ocaml.5.4.1 --deps-only\neval $(opam env)\nopam install . --deps-only\ndune build\n```\n\n## Dependencies\n\nOnton shells out to several external tools and talks to the GitHub API. All\nof these must be installed and configured before onton can run.\n\n### Runtime dependencies\n\n| Tool | Purpose | Install |\n|------|---------|---------|\n| `git` | Worktree CRUD, branch detection, rebase | `brew install git` (or system package manager) |\n| `gh` (GitHub CLI) | Token resolution, PR discovery (`gh pr list`), and the main vehicle agents use to interact with GitHub (`gh pr create`, `gh pr edit`, `gh pr view`, `gh api`, `gh api graphql`) | `brew install gh`, then `gh auth login` |\n| Coding-agent CLI | Drives the actual patches. One of: `claude` ([Claude Code](https://docs.anthropic.com/en/docs/claude-code)), `codex` ([OpenAI Codex CLI](https://github.com/openai/codex)), `opencode` ([OpenCode](https://opencode.ai)), `pi`, `gemini` ([Gemini CLI](https://github.com/google-gemini/gemini-cli)). Selected via `--backend` (default `claude`) and `--model` (see [Backend \u0026 model](#backend--model) below). Must be on `PATH` | See each tool's docs |\n\nOnton is built and tested on macOS (ARM64 and x86_64). Linux should work but is\nnot part of the release pipeline.\n\n### GitHub authentication\n\nOnton talks to GitHub two ways and both need credentials:\n\n1. **The OCaml binary** opens its own HTTPS connection to `api.github.com`\n   (REST + GraphQL) for everything it does itself: polling, PR discovery at\n   startup, merge-state lookups, draft toggles, base updates, etc. It needs a\n   token in `Authorization: bearer …`. The only `gh` invocation from the\n   binary itself is `gh auth token` during startup, used solely as a fallback\n   to source that token.\n2. **The agent processes** shell out to `gh` for higher-level operations like\n   creating PRs (`gh pr create`), editing PR bodies (`gh pr edit`), and\n   calling `gh api` / `gh api graphql`. `gh` uses its own credential store\n   seeded by `gh auth login`.\n\n#### Token resolution order\n\nThe token used by the OCaml binary is resolved in this order (see\n`bin/main.ml:47`):\n\n1. `--token` CLI flag\n2. `GITHUB_TOKEN` environment variable\n3. `gh auth token` (executed as a subprocess)\n\nIf all three are empty, onton refuses to start with\n`--token / GITHUB_TOKEN is required`.\n\nThe simplest setup is therefore: install `gh`, run `gh auth login`, and let\nonton pick up the token automatically. Confirm with `gh auth token` that a\nnon-empty value is printed.\n\n#### Token scopes\n\nThe token must be able to read and write PRs, issue comments, and check runs\non the target repository.\n\n- **Classic personal access token**: enable `repo` (full control of private\n  repositories — needed because we read PR state, post comments, change base\n  branches, and toggle draft status) and `read:org` if the repo lives under a\n  GitHub organization with SSO. `workflow` is required if any patch touches\n  files under `.github/workflows/`.\n- **Fine-grained personal access token** (recommended): scope it to the\n  specific repository and grant these repository permissions:\n  - **Contents**: Read and write (worktree pushes, branch metadata)\n  - **Pull requests**: Read and write (create/update PRs, change base, toggle\n    draft, merge state)\n  - **Issues**: Read and write (PR review/issue comments share the issues API)\n  - **Checks**: Read (CI status polling)\n  - **Commit statuses**: Read (legacy CI status polling)\n  - **Metadata**: Read (always required)\n  - **Workflows**: Read and write — only if patches will modify\n    `.github/workflows/*`\n- **GitHub App installation token**: same permissions as the fine-grained PAT\n  list above. Pass it via `--token` or `GITHUB_TOKEN`; `gh` will not produce\n  installation tokens for you.\n- **SSO-protected orgs**: after creating the token, click \"Configure SSO\" on\n  the token page and authorize it for the org, otherwise every API call returns\n  `401`.\n\nIf you see `403`/`401` from the poller, the most common causes are: missing\nSSO authorization, missing `Pull requests: write` (for draft/base updates), or\na fine-grained token that wasn't granted access to the specific repository.\n\n#### `gh` configuration\n\n`gh auth login` (choose HTTPS, authenticate via browser or paste a token)\nconfigures `gh` itself. Verify with:\n\n```sh\ngh auth status        # shows which host and scopes are active\ngh auth token         # prints the token onton will pick up\ngh pr list --limit 1  # smoke-test repo access\n```\n\nIf you'd rather keep `gh`'s token separate from onton's, set `GITHUB_TOKEN`\nexplicitly and `gh` will prefer that variable too — keeping both in sync.\n\n#### SSH transport\n\nOAuth tokens have per-scope restrictions enforced by GitHub even when the\npush itself looks routine. The most common gotcha: a token without the\n`workflow` scope is refused on any push that touches `.github/workflows/*`,\nwith a clear `remote: refusing to allow an OAuth App to create or update\nworkflow … without 'workflow' scope` message — and onton now classifies that\nas `workflow_scope_missing` and routes the agent straight to\n`needs_intervention` instead of retrying.\n\nSSH authentication is not subject to those per-scope restrictions. If you\nalready maintain a sibling clone of `owner/repo` under one of `$PWD/..`,\n`~/code-src/`, `~/src/`, `~/code/`, `~/dev/`, or `~/projects/` whose `origin`\nuses SSH (`git@github.com:owner/repo.git`), onton will inherit the same SSH\ntransport for its managed clone — you'll see\n`onton: detected SSH sibling clone at … — cloning managed repo via SSH` at\nstartup, and `config.json` will record `url_scheme: \"ssh\"`. With no SSH\nsibling present, the managed clone defaults to HTTPS as before. SSH pushes\nflow through your ssh-agent / `~/.ssh/config`, so onton's OAuth token does\nnot gate workflow changes.\n\n#### Manual git inside a worktree\n\nThe per-patch worktrees under `~/worktrees/\u003cproject\u003e/patch-\u003cN\u003e` are vanilla\ngit checkouts; onton's `GIT_ASKPASS` injection does not apply when you run\ngit there yourself. If you ever need to fetch or push from a worktree by\nhand and your interactive shell isn't authenticated for HTTPS pushes, run\n`gh auth setup-git` once — it installs gh's credential helper into your\nglobal git config and subsequent git invocations transparently reuse the\ntoken.\n\n### Coding-agent authentication\n\nOnton spawns each patch in an isolated config dir (`spawn-envs/\u003cpatch_id\u003e/{claude,codex,opencode}`)\nso that concurrent agents don't fight over a single auth file during token\nrefresh. It then seeds those dirs with symlinks to the user's real auth files.\nThis works transparently on Linux (file-based credentials) and on macOS for\ncodex / opencode, but not for **Claude Code on macOS**: Claude stores its\nOAuth credential in the macOS Keychain, and a per-patch `CLAUDE_CONFIG_DIR`\nprevents the spawned Claude from finding that Keychain entry. The result is\n`Not logged in · Please run /login` even though `claude` works fine in your\nown shell.\n\nThe fix is to give onton a long-lived OAuth token to inject as\n`CLAUDE_CODE_OAUTH_TOKEN` (precedence #5 in [Claude Code's credential\ndocs](https://code.claude.com/docs/en/iam#authentication-precedence)).\n\n```sh\n# 1. Generate a 1-year token from your existing subscription (browser OAuth).\nclaude setup-token\n\n# 2. Save it where onton will pick it up. Mode 0600 — it's a credential.\nmkdir -p ~/.config/onton\ninstall -m 600 /dev/null ~/.config/onton/claude-oauth-token\ncat \u003e ~/.config/onton/claude-oauth-token\nchmod 600 ~/.config/onton/claude-oauth-token\n```\n\nPaste the token at the `cat` prompt, then press `Ctrl-D`.\n\nOnton checks `CLAUDE_CODE_OAUTH_TOKEN` in the parent env first (for users who\nprefer to export it from their shell rc), falling back to\n`$XDG_CONFIG_HOME/onton/claude-oauth-token` (or `~/.config/onton/claude-oauth-token`).\nLinux users don't need this — the symlinked `.credentials.json` handles auth —\nbut the token file works there too if you'd rather use it.\n\n### Optional: per-repo and project state directories\n\nOnton writes to two locations on disk. Neither requires setup but both are\nworth knowing about:\n\n- `~/.local/share/onton/\u003cproject\u003e/` — durable project state (snapshot,\n  message ledger, transcripts). Created on first run.\n- `~/.config/onton/\u003cowner\u003e/\u003crepo\u003e/` — per-repo user configuration, including\n  the `on_worktree_create` hook described below. You create this directory by\n  hand if you want a hook.\n\n### Building from source (development only)\n\nIn addition to the runtime dependencies above, building from source needs the\nOCaml toolchain listed under [Option C](#option-c-from-source): OCaml 5.4.1,\ndune 3.21, and opam. `opam install . --deps-only` installs the rest\n(`base`, `eio`, `cmarkit`, `re`, `cmdliner`, `qcheck`, etc.).\n\n## Usage\n\n```sh\nonton --gameplan GAMEPLAN [OPTIONS]       # Start a new project from a gameplan\nonton PROJECT [OPTIONS]                  # Resume a saved project\nonton --repo ../my-repo [OPTIONS]        # Ad-hoc mode (no gameplan)\n```\n\n| Flag | Default | Description |\n|------|---------|-------------|\n| `PROJECT` | (derived from gameplan) | Project name (positional). Required to resume, optional with `--gameplan` |\n| `--gameplan` | — | Path to the gameplan markdown file |\n| `--repo` | `.` | Path to the git repository. GitHub owner/repo are inferred from `git remote` |\n| `--token` | `$GITHUB_TOKEN` or `gh auth token` | GitHub API token |\n| `--backend` | `claude` | LLM backend: `claude`, `codex`, `opencode`, `pi`, `gemini`. See [Backend \u0026 model](#backend--model) |\n| `--model` | (backend CLI's own default) | Model name passed to the backend CLI |\n| `--main-branch` | (auto-detected) | Main branch name (inferred from remote HEAD if omitted) |\n| `--poll-interval` | `30.0` | GitHub polling interval in seconds |\n| `--max-concurrency` | `5` / `$ONTON_MAX_CONCURRENCY` | Maximum concurrent Claude processes |\n| `--headless` | off | Run without TUI (plain log output to stdout) |\n\nProject config and state are persisted to `~/.local/share/onton/\u003cproject\u003e/`.\nResuming a project reloads the saved snapshot (including agent transcripts) and\nreconciles against GitHub. The snapshot includes the durable patch-agent\nmessage ledger, so accepted but incomplete work can resume after restart.\n\n### User configuration\n\nPer-repo configuration lives at `~/.config/onton/\u003cgithub-owner\u003e/\u003cgithub-repo\u003e/`.\n\n| File | Description |\n|------|-------------|\n| `on_worktree_create` | Executable hook run after a new git worktree is created |\n\nThe `on_worktree_create` hook receives these environment variables:\n\n| Variable | Description |\n|----------|-------------|\n| `ONTON_WORKTREE_PATH` | Absolute path to the created worktree |\n| `ONTON_PATCH_ID` | Patch identifier |\n| `ONTON_BRANCH` | Branch name |\n\nExample — install dependencies in every new worktree:\n\n```sh\nmkdir -p ~/.config/onton/myorg/myrepo\ncat \u003e ~/.config/onton/myorg/myrepo/on_worktree_create \u003c\u003c 'EOF'\n#!/bin/bash\ncd \"$ONTON_WORKTREE_PATH\"\nnpm install\nEOF\nchmod +x ~/.config/onton/myorg/myrepo/on_worktree_create\n```\n\nWorktrees are discovered from `git worktree list`. If no existing worktree is\nfound for a patch's branch, one is created at\n`~/worktrees/\u003cproject\u003e/patch-\u003cid\u003e`.\n\n### Ad-hoc mode\n\nWhen launched without `PROJECT` or `--gameplan`, onton starts with an empty\npatch list. Add PRs at runtime with `+N` in text mode (`:` then `+123`). Each\n`+N` creates a new agent that polls and responds to the given PR. Branch, base,\nand worktree are auto-discovered from GitHub and local git.\n\nState is persisted across restarts — ad-hoc agents survive session restarts and\nresume where they left off.\n\n## Build \u0026 test\n\n```sh\ndune build          # compile with strict warnings (most warnings are fatal)\ndune runtest        # inline tests + property tests (QCheck2)\ndune build @check   # type-check only (no linking), faster for quick feedback\ndune exec bin/main.exe -- --gameplan path/to/gameplan.md\ndune fmt            # auto-format via ocamlformat\n```\n\n## Architecture\n\n```\ngameplan.md ──\u003e Gameplan_parser ──\u003e Graph + Patches\n                                         │\n                  Patch_controller ──────┤\n                    ├── poll ingestion + reconciliation\n                    ├── GitHub lifecycle effects\n                    ├── durable patch-agent message planning\n                    └── runnable work derivation\n                                         │\n                    Orchestrator ────────┤\n                    ├── Patch_agent (per-patch state machine)\n                    ├── Patch_decision (pure decision logic)\n                    ├── Poller (GitHub PR status via GraphQL)\n                    ├── Reconciler (merge detection, stale base detection)\n                    └── TUI (terminal display + markdown transcript)\n\n          ┌─────────────────────────────────────────────┐\n          │              Eio_main.run                   │\n          │  ┌────────┐ ┌───────┐ ┌───────┐ ┌────────┐  │\n          │  │  TUI   │ │Poller │ │Runner │ │Persist │  │\n          │  │ fiber  │ │ fiber │ │ fiber │ │ fiber  │  │\n          │  └───┬────┘ └───┬───┘ └───┬───┘ └───┬────┘  │\n          │      └──────────┼─────────┼─────────┘       │\n          │            Runtime (Eio.Mutex)              │\n          └─────────────────────────────────────────────┘\n```\n\n### Claude backend session management\n\nClaude is invoked via `-p` (prompt mode, not `--print`) which saves sessions,\nenabling `--continue` to resume the most recent session in a worktree. This\nenables session resumption across restarts. Claude is spawned directly against\npipes — no PTY wrapper — and any stray control characters in the stream are\nstripped defensively before JSON parsing.\n\nThe session fallback chain: `--continue` (resume worktree session) -\u003e fresh\nsession (no `--continue`) -\u003e give up (needs intervention). If `--continue`\nproduces no events, it's treated as a resume failure and falls back to fresh.\n\nAdditional flags: `--dangerously-skip-permissions`, `--max-turns 200`,\n`--output-format stream-json`, `--verbose`.\n\n### Patch-agent message delivery\n\nRunnable patch work is materialized as durable messages, not just ephemeral\nrunner actions. The controller writes those messages to an outbox with stable\nlogical IDs. The runner then:\n\n1. accepts a pending message exactly once, which durably acknowledges it and\n   fires the corresponding patch transition\n2. executes the Claude or rebase work for that accepted message\n3. marks the message completed when the patch finishes the operation\n\nIf onton crashes after acceptance but before completion, the same acknowledged\nmessage is resumed on the next tick instead of creating a new one or replaying\nthe original queue-consuming transition.\n\n### Runner concurrency\n\nAction fibers are spawned independently (`fork_daemon`) rather than batched —\nthe runner loop picks up newly-queued operations on each 1-second cycle without\nwaiting for running sessions to finish. Backpressure is provided by a\n`max_concurrency` semaphore.\n\n### Modules\n\n| Module | Purpose |\n|--------|---------|\n| `types` | Core types: `Patch_id`, `Branch`, `Operation_kind`, `Patch`, `Comment`, `Gameplan` |\n| `priority` | Operation priority queue — single source of truth for ordering |\n| `graph` | Dependency graph: unblocked detection, base branch resolution |\n| `gameplan_parser` | Markdown gameplan to structured `Gameplan.t` |\n| `patch_agent` | Per-patch state machine: start, respond, complete, rebase transitions (private type). Tracks `current_op`, current accepted message, and generation |\n| `patch_controller` | Pure evergreen controller: poll ingestion, lifecycle reconciliation, GitHub effects, and durable patch-agent message planning |\n| `patch_decision` | Pure decision logic: disposition, CI cap, review comment filtering, merge conflict handling. Extracted from main.ml for testability |\n| `llm_backend` | Backend interface: process spawning, stream event parsing, session management |\n| `claude_backend` | Claude Code backend implementation |\n| `codex_backend` | OpenAI Codex backend implementation |\n| `opencode_backend` | OpenCode backend implementation |\n| `pi_backend` | Pi coding agent backend implementation |\n| `claude_process` | Claude CLI session state machine (No_session -\u003e Has_session) |\n| `claude_runner` | Claude subprocess spawning, NDJSON streaming, defensive control-char stripping, `got_events` resume-failure detection |\n| `spawn_logic` | Pure spawn/scheduling logic: which patches to run next |\n| `orchestrator` | Durable patch state plus primitive transitions, including the patch-agent outbox |\n| `reconciler` | Pure merge detection, rebase cascading, stale base detection, liveness enforcement |\n| `startup_reconciler` | PR discovery, worktree recovery, stale busy reset at startup |\n| `poller` | GitHub polling: comments, CI, merge conflicts, merge/approval state |\n| `state` | Spec context maps (PatchCtx, Comments) for invariant checking |\n| `runtime` | Mutex-protected shared snapshot across fibers (orchestrator + activity log + gameplan + transcripts) |\n| `activity_log` | Per-patch event, transition, and stream entry feed |\n| `event_log` | Structured event log for persistence and replay |\n| `pr_state` | Pull request state tracking and derived status |\n| `run_classification` | Classify agent run outcomes (success, failure, needs intervention) |\n| `forge` | Git forge (GitHub) abstraction |\n| `invariants` | Pure spec invariant checker over `State.t` — used by property tests and ad-hoc snapshot inspection (no production call site) |\n| `persistence` | JSON snapshot save/load for the current durable schema, including transcripts and the message ledger |\n| `project_store` | Project config and gameplan storage at `~/.local/share/onton/` |\n| `user_config` | Per-repo user configuration and hook execution from `~/.config/onton/\u003cowner\u003e/\u003crepo\u003e/` |\n| `prompt` | Agent prompt rendering with per-project template override support |\n| `worktree` | Git worktree CRUD, branch detection, orchestrator-executed `git rebase` |\n| `github` | GitHub GraphQL API client (HTTPS via Eio) |\n| `term` | ANSI terminal primitives (raw mode, key input, size, SIGTSTP/SIGCONT) |\n| `tui_input` | Keyboard -\u003e command translation, text-mode parsing, history buffer |\n| `tui` | Terminal UI: list/detail/timeline views, status derivation, frame rendering, gameplan-ordered display |\n| `markdown_render` | CommonMark to ANSI terminal renderer via cmarkit |\n\n### Design principles\n\n- **Eio for structured concurrency** — four fibers (TUI, poller, runner,\n  persistence), with independently-spawned Claude action fibers bounded by a\n  semaphore\n- **Pure logic core** — parser, graph, priority, state machine, decision logic,\n  controller reconciliation, and message planning are pure functions with no\n  I/O\n- **Strict compiler feedback** — all warnings fatal (except 44/70), `.mli`\n  files enforce module boundaries\n- **Pantagruel spec alignment** — state machine transitions match the formal\n  spec\n- **Single source of truth** — priority ordering defined once in `Priority`;\n  sorted patch display via shared `sorted_patch_ids` ref; `patch_controller`\n  owns deterministic follow-up decisions; `current_op` plus the durable outbox\n  track active and resumable work\n- **Property-based testing** — QCheck2 tests for graph, patch agent,\n  controller, orchestrator liveness, reconciler, delivery-aware state-machine\n  behavior, persistence roundtrip, stream parsing, TUI input, poller, and\n  patch decision\n\n## CI\n\nGitHub Actions runs on every push and PR:\n\n- **Build \u0026 Test** — `dune build` + `dune runtest` with compiler error annotations on PR diffs\n- **Property tests** — QCheck2 with 10,000 iterations\n- **Format check** — `ocamlformat` via `ocaml/setup-ocaml/lint-fmt`\n\n## Backend \u0026 model\n\nTwo flags control which agent runs the patches:\n\n- `--backend BACKEND` — one of `claude`, `codex`, `opencode`, `pi`, `gemini`.\n  Default: `claude`.\n- `--model MODEL` — model name passed through to the backend's CLI. When\n  omitted, onton does not pass `--model` to the underlying CLI, so each\n  provider's own default applies.\n\n```sh\nonton --backend claude --model sonnet-4-6\nonton --backend claude --model opus\nonton --backend codex --model gpt-5-codex\nonton --backend gemini --model gemini-2.5-pro\nonton --backend opencode --model anthropic/claude-sonnet-4-5\n```\n\nBoth flags are persisted in project config and reused on resume unless\noverridden. Stored configs written by older onton versions (where `backend`\nwas `claude-opus` or `claude-sonnet`) are migrated to the decomposed shape\nautomatically on load.\n\n### Per-repo defaults (`config.json`)\n\nTo avoid re-typing `--backend` / `--model` for every fresh run in a repo,\nwrite a per-repo config at\n`~/.config/onton/\u003cowner\u003e/\u003crepo\u003e/config.json`:\n\n```jsonc\n{\n  \"default\": {\n    \"backend\": \"codex\",\n    \"model\":   \"auto\"\n  },\n  \"routing\": {\n    \"1\": { \"backend\": \"claude\", \"model\": \"haiku\"   },\n    \"3\": { \"backend\": \"codex\",  \"model\": \"gpt-5.5\" }\n  }\n}\n```\n\nResolution order, evaluated per field independently:\n\n1. CLI flag (`--backend` / `--model`)\n2. Previously stored value from the project store (resume only)\n3. `default.backend` / `default.model` from `config.json`\n4. Built-in (`claude`; model unset)\n\n`model: \"auto\"` from any source — the CLI flag or `default.model` —\nactivates the `routing` map: each patch's complexity (1/2/3) picks its\nown `(backend, model)`, falling back to the effective backend's hardcoded\nladder for tiers with no entry. Both subfields of `default` are\noptional; pin just `backend`, just `model`, or both. Run\n`onton-check-repo-config \u003cowner\u003e \u003crepo\u003e` to verify how a `config.json`\nparses.\n\n### Supported models\n\nOnton passes `--model` through to the backend CLI verbatim, so any model the\nunderlying CLI accepts will work. Use unpinned aliases (e.g. `sonnet`,\n`opus`) when you want \"current best in tier\"; pin a specific version when you\nneed reproducibility. The names below are accurate as of February 2026 —\n**check each provider's docs for the current list**:\n\n| Backend | Common model names | Source of truth |\n|---------|-------------------|-----------------|\n| `claude` | `opus`, `sonnet`, `haiku` (unpinned aliases); `claude-opus-4-7`, `claude-sonnet-4-6`, `claude-haiku-4-5` (pinned) | [Anthropic models](https://docs.anthropic.com/en/docs/about-claude/models) |\n| `codex` | `gpt-5-codex`, `gpt-5`, `gpt-5-mini` | [OpenAI models](https://platform.openai.com/docs/models), [Codex CLI README](https://github.com/openai/codex) |\n| `gemini` | `gemini-2.5-pro`, `gemini-2.5-flash` | [Gemini API models](https://ai.google.dev/gemini-api/docs/models) |\n| `opencode` | Provider-prefixed, e.g. `anthropic/claude-sonnet-4-5`, `openai/gpt-5` | [OpenCode docs](https://opencode.ai/docs) |\n| `pi` | Run `pi --help` for the current list | Pi CLI |\n\nPushing a `v*` tag builds macOS ARM64 and x86_64 binaries, creates a GitHub\nrelease, and updates the Homebrew formula.\n\n## TUI\n\nThree view modes:\n- **List view** — patch table with status badge, PR number, title (branch\n  name for ad-hoc), current operation, CI failures\n- **Detail view** — single patch: status, branch, base, worktree path, PR,\n  dependencies, conflict, pending comments, CI checks. Scrollable\n  markdown-rendered transcript with timestamped prompt delivery and Claude\n  responses. Info section pinned at top; transcript auto-follows new content\n- **Timeline view** — scrollable activity log (transitions, events, stream\n  entries)\n\nKey bindings:\n\n| Key | List view | Detail view | Timeline |\n|-----|-----------|-------------|----------|\n| `j`/`k`, arrows | Navigate patches | Scroll transcript | Scroll log |\n| `Enter` | Open detail | Enter text mode | — |\n| `Esc`/`Backspace` | — | Back to list | Back to list |\n| `t` | Timeline | Timeline | List |\n| `h` | Help overlay | Help overlay | Help overlay |\n| `q` | Quit | Quit | Quit |\n\nText mode (Enter in detail view, or `:` in list):\n- Type a message and press Enter — sent as human message to the currently\n  viewed patch (clears `needs_intervention`)\n- `N\u003e message` — send human message to patch N\n- `+123` — register ad-hoc PR #123 for the selected patch\n- `w /path` — register existing worktree directory\n- `-` — remove the selected patch from orchestration\n\nThe input prompt is visible in the footer as `: \u003ctext\u003e`.\n\nHeadless mode (`--headless`) outputs plain timestamped log lines to stdout with\ndedup-based entry tracking.\n\n## Formal spec\n\nThe state machine is specified in\n[Pantagruel](https://github.com/subsetpark/pantagruel). Key\nproperties:\n\n- Sessions are never lost (`has_session p -\u003e has_session' p`)\n- Merged is absorbing (terminal state)\n- Queue isolation (responding to `k` only removes `k`)\n- CI failure cap (3 failures triggers intervention)\n- Liveness (all fireable actions fire)\n- `approved?` is derived: `has_pr \u0026\u0026 merge_ready \u0026\u0026 not busy \u0026\u0026 not\n  needs_intervention \u0026\u0026 base_branch = main` (where `merge_ready` reflects\n  GitHub's `mergeStateStatus = CLEAN`). Patches targeting a dependency branch\n  show `blocked-by-dep` instead\n\n## Docs site\n\nThe project documentation is hosted at [write-gameplan.dev](https://write-gameplan.dev)\nand deployed to Vercel under the Flowglad org.\n\nThe site is static HTML in the `docs/` directory. To update it:\n\n1. Edit files in `docs/` (HTML pages, `assets/styles.css`, etc.)\n2. Deploy with the Vercel CLI:\n   ```sh\n   vercel --scope flowglad --prod\n   ```\n\nA `.vercelignore` ensures only `docs/` and `vercel.json` are uploaded.\n\n## License\n\nBSD-3\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflowglad%2Fonton","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflowglad%2Fonton","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflowglad%2Fonton/lists"}