Skip to content
outrightmentalPublic

About

Yoke a team of coding agents to work through a GitHub issue queue

Resources

Stars

1 star

Watchers

1 watching

Forks

Latest commit

Β 

History

180 Commits

Folders and files

Repository files navigation

Test

Yoke

Yoke a team of coding agents to work through a GitHub issue queue.

yoke is a TypeScript orchestrator that closes the loop on agentic software development. Write issues. Yoke handles the rest: it picks up each issue, asks Claude to implement it in a local checkout, opens a draft pull request, self-reviews the diff, fixes anything that needs fixing β€” review comments, merge conflicts, failing CI β€” writes a polished final description, and squash-merges. Then it moves on to the next issue.

It is for developers who want the creative part of software development β€” expressing intent and reviewing outcomes β€” without babysitting every agent handoff.

Yoke by Outright Mental

Why this exists

Modern coding agents are powerful, but they still need a conductor β€” someone to decide what starts next, avoid overloading the repo, notice blocked tasks, run a review pass, route fixes back for another round, preserve closing references, and merge the finished work.

yoke makes that conductor programmable and autonomous, with Claude as the worker behind every step.

Give your repository a prioritized issue backlog and run the loop. The project becomes a living assembly line:

issues β†’ Claude implementation β†’ PR β†’ self-review β†’ fixes β†’ squash merge

For the solo developer

Yoke is a force multiplier for a solo developer who wants to stay in the creative and strategic flow. You decide what matters β€” write the issues, set the acceptance criteria, shape the architecture. Yoke handles the mechanical work:

  • Picks up the next unblocked issue automatically, so you never lose momentum between tasks.
  • Implements, reviews, and merges in the background while you focus on design, testing, and product direction.
  • Resolves merge conflicts quietly β€” when branches diverge, Yoke asks Claude to rebase and fix the conflicts before continuing the review cycle, without you ever touching a rebase command.
  • Fixes failing CI without being asked β€” it reads the check logs and pushes a fix.
  • Keeps the PR queue clean β€” only finished, merged work accumulates; no half-baked draft PRs or stale branches.
  • Respects your control via the manual label β€” apply it to any issue or PR you want to keep under your direct hand.

The result: you stay in your highest-value role while Yoke pulls weight in the background to maintain the project's forward momentum.

SDLC decision tree

The diagram below shows the full lifecycle of an issue through Yoke, covering both Simple SDLC (fully automated, the default) and Project SDLC (human-in-the-loop via a GitHub Projects board).

flowchart TD
    ISSUE(["πŸ—‚ GitHub Issue (open)"])

    ISSUE --> MANUAL{"Labeled\n'manual'?"}
    MANUAL -->|Yes| SKIP_MANUAL["⏭ Skipped β€” human-only"]
    MANUAL -->|No| DEPS{"Open\nblockers?"}
    DEPS -->|Yes| WAIT_DEP["⏸ Wait β€” blocked until\ndependency closes"]
    DEPS -->|No| MODE{"Project\nmode?"}

    MODE -->|"Simple SDLC\n(default)"| IMPLEMENT
    MODE -->|"Project SDLC\n(github_project_number set)"| READY{"Issue status = Ready,\nor In Progress with\nno open PR?"}
    READY -->|No| SKIP_READY["⏭ Skipped β€” not Ready,\nand not In Progress\nwithout open PR"]
    READY -->|Yes| IMPLEMENT

    IMPLEMENT["βš™ Claude: Implement issue\nin fresh local checkout\ncommit + push branch"]
    IMPLEMENT --> OPEN_PR["πŸ“¬ Yoke opens draft Pull Request"]

    OPEN_PR --> CONFLICTS{"Merge\nconflicts?"}
    CONFLICTS -->|Yes| RESOLVE["πŸ”§ Claude: Rebase branch\nresolve conflicts + push"]
    RESOLVE --> CONFLICTS
    CONFLICTS -->|No| CI{"CI checks\nstatus"}
    CI -->|"Failing or\nstuck > 10 min"| FIX_CI["πŸ”§ Claude: Read CI logs\nfix failures + push"]
    FIX_CI --> CI
    CI -->|Passing| REVIEW["πŸ” Claude: Self-review\nthe PR diff"]

    REVIEW --> REVIEW_RESULT{"Changes\npushed?"}
    REVIEW_RESULT -->|Yes β€” pushed fixes| CI
    REVIEW_RESULT -->|"No β€” clean"| BRANCH{"Project\nmode?"}

    BRANCH -->|Simple SDLC| REVIEW2["πŸ” Claude: Second\nself-review pass"]
    REVIEW2 --> RESULT2{"Changes\npushed?"}
    RESULT2 -->|Yes| CI
    RESULT2 -->|"No β€” second\nclean pass"| MERGE["βœ… Yoke: Generate final PR body\n+ squash merge"]
    MERGE --> CLOSED(["πŸŽ‰ Issue auto-closed by GitHub"])

    BRANCH -->|Project SDLC| REQUEST["πŸ‘€ Yoke: Mark PR ready-for-review\nrequest human review\nmove issue β†’ In Review"]
    REQUEST --> HUMAN{"Human\naction"}
    HUMAN -->|"Converts PR to draft\nor adds comments\nor moves issue back to Ready"| REVIEW
    HUMAN -->|"Approves and merges"| CLOSED
Loading

Simple SDLC

In Simple SDLC mode β€” the default for any project whose env.yaml entry has no github_project_number β€” Yoke runs fully autonomously from issue to merged PR:

  1. Any open, unblocked, non-manual issue is eligible to start.
  2. Issues are prioritized bugs-first, then by milestone, then by creation time.
  3. Claude implements, the PR opens as a draft, Claude self-reviews twice (CI gates apply after any code-changing pass; two consecutive clean passes trigger the merge), and Yoke squash-merges β€” no human action required.
  4. Merge conflicts and CI failures are handled quietly in the background.

This mode is ideal for personal projects, greenfield work, and any context where CI and branch protections serve as the safety net.

Project SDLC (Human-in-the-Loop)

Enable Project SDLC per project by setting github_project_number in env.yaml to a GitHub Projects v2 board number. This mode adds a human review gate before any merge:

  1. Issues in Ready status are picked up, and issues already in In Progress are also picked up if they have no open PR linked and no active agent session already running.
  2. When work starts on a Ready issue, it moves to In Progress.
  3. After one clean self-review, Yoke marks the PR ready-for-review, requests human review, and moves the issue to In Review β€” it never auto-merges.
  4. Yoke resumes work automatically if:
    • A human converts the PR back to a draft (wants more changes).
    • A new review comment arrives on the PR.
    • The issue is moved back to Ready on the project board.

List the GitHub logins to notify when a PR is ready under the project's reviewers key in env.yaml.

This mode suits teams where a human QA or architect approves each merge, while Yoke handles the full implementation-review-fix loop.

What it does

On every iteration, yoke:

  1. Loads open GitHub issues, open pull requests, pending workflow approvals, and local agent-session state.
  2. Builds a dependency-aware work plan from issue age plus relationships like blocked by #123, depends on #123, and blocks #123.
  3. Enforces a configurable concurrency limit so the repo does not get flooded with half-finished work.
  4. For each eligible issue, runs Claude locally in a fresh checkout to implement the change and open a draft pull request.
  5. Checks CI status on every open PR β€” waits for passing checks before advancing, fixes failing checks, and escalates checks stuck for more than 10 minutes.
  6. Detects merge conflicts and asks Claude to resolve them before continuing.
  7. Asks Claude to self-review the diff and push fixes if needed. Requires two consecutive clean self-reviews before advancing.
  8. Generates a polished final PR description with Claude, updates the PR body, preserves closing references, and squash-merges. Retries with --admin if GitHub's branch policy requires it.

When the Claude CLI reports that the subscription's usage limit is exhausted, yoke reads the reset time out of the CLI's own message and treats it as a hard hold on the whole engine pool: nothing is planned, claimed, checked out or run until it passes, and work resumes by itself the moment it does. GitHub-only maintenance (workflow approvals, reconciliation, the activity feed) keeps running throughout.

The hold is said once, not once per engine per cycle:

  • In the terminal, a single line updates in place for the duration β€” waiting 2h 14m until Sep 21, 8:50 AM for Claude usage limit β€” with no repeated checkout output and no repeated identical failures behind it.
  • On the Dashboard, a banner across the top of the page shows what is being waited on, how much is left, and the wall-clock time it ends. It is replayed to a browser that connects mid-hold, so a reload never makes a parked run look merely idle.

GitHub rate-limit holds get the same treatment.

The big idea

yoke treats GitHub as the source of truth and Claude as the worker behind every action:

  • Issues are intent. Write clear issues and dependencies; the loop decides when they are safe to start.
  • Pull requests are work cells. Each PR moves through review, fix, re-review, and merge phases automatically.
  • Local session state is memory. A small persisted session store prevents duplicate work and lets each loop cycle pick up where the last one stopped.
  • Humans stay in control. You own the backlog, repository settings, branch protections, CI, and review standards.

See the deeper docs:

Quick start

Install dependencies:

npm install

Log in to Claude Code (once, if not already done):

claude login

Create env.yaml from the template, then add a GitHub PAT under github_tokens and the repositories to run under projects:

cp env.example.yaml env.yaml
# edit env.yaml: set github_tokens[0].token and projects[].github_repository

env.yaml is the only place Yoke reads settings from (it is git-ignored). See Configuration for every key.

Run a safe one-shot preview:

npm start -- --dry-run --once

Run the real loop:

npm start

Yoke loads env.yaml from the current working directory and works every repository listed under projects; there are no positional CLI arguments.

Dashboard

yoke opens a single real-time Dashboard in your browser at http://localhost:3000 (change the port with dashboard_port) when the loop starts. One dashboard covers all configured projects: there is a single shared pool of max_concurrency engine cylinders, and each project's own max_concurrency caps how many of those cylinders may work it at once. When more than one project is configured, every cylinder, lifecycle pill, broadcast-feed card, and event-log line is labelled with the project (owner/repo) it belongs to; with a single project the name appears in the header only. The Dashboard shows:

  • Issue β†’ PR Lifecycle panel: a row of two-halved pills, one per open issue. The left half shows the issue; the right half shows the linked pull request and transitions through states:

    • (absent) β€” no PR yet
    • dotted outline β€” implementation is planned for this iteration
    • solid outline, draft β€” PR is open as a draft
    • solid outline, ready β€” PR is open and ready for review
    • completed (full fill) β€” PR is merged/closed

    Each pill is colour-coded to its issue's own stable colour β€” derived from the issue number, so an issue keeps the same colour forever β€” and the broadcast feed uses that same colour for everything pertaining to the issue.

  • Implementation / Review / Broadcast Feed panels showing live orchestrator logs and GitHub activity.

  • Keep-awake lamp: the ON light in the top-right corner is not a CSS animation β€” it is a tiny looping, muted video, playing for as long as the Dashboard is open. An actively playing video is the one thing observed to stop a locked-down laptop sleeping or locking mid-run, so the Dashboard plays one itself instead of you parking a full-screen YouTube video next to it. There is nothing to switch on: it runs whenever the Dashboard is open, not only while cylinders are working, and the Screen Wake Lock API is requested alongside it wherever the browser allows. If the browser refuses to autoplay the loop, the lamp turns amber and reads CLICK β€” one click or keypress anywhere on the page starts it. The lamp is sized to the height of the header's own text, so it lights the corner without making the header bar taller.

To prevent the Dashboard from opening automatically, pass --no-browser:

npm start -- --no-browser

The Dashboard server still starts; the URL is printed to stdout so you can open it manually.

Graceful shutdown: Press Escape while the loop is running to let Yoke finish any in-flight actions before exiting. Press Ctrl+C to exit immediately.

Requirements

  • Node.js 18+
  • git on PATH.
  • A GitHub PAT, listed under github_tokens in env.yaml.
  • The claude CLI (Claude Code) installed locally, on PATH, and logged in via claude login. Uses your Claude Code subscription β€” no API key required.

Configuration

All configuration lives in env.yaml in the working directory; copy env.example.yaml to create it. No configuration is read from environment variables. Yoke uses the GitHub PAT from github_tokens directly for API calls and Git clone/fetch/push operations. Claude Code authentication is still handled by the claude CLI: ANTHROPIC_API_KEY, GITHUB_TOKEN, GH_TOKEN, and YOKE_GITHUB_TOKEN (plus the legacy VIBRATOR_GITHUB_TOKEN) are removed from the environment of every Claude subprocess, so the agent authenticates only through your Claude Code login and the configured PAT.

GitHub token permissions

Fine-grained PATs need access to the target repository with:

  • Metadata: read
  • Contents: read/write
  • Pull requests: read/write
  • Issues: read/write
  • Actions: read/write
  • Checks: read
  • Commit statuses: read
  • Projects: read/write, if using project mode
  • Workflows: write, if the agent may push workflow file changes

Classic PATs may need repo, project when using project mode, and workflow when the agent may edit or push workflow files.

Global keys (top level of env.yaml)

Key Default Purpose
github_tokens β€” Required. List of named GitHub PATs, each { name, token, default? }. Projects that set no github_token_name use the entry marked default: true, or the first entry.
projects β€” Required. List of repositories to run; see the per-project keys below.
max_concurrency 3 Total size of the shared engine-cylinder pool across all projects.
cycle_minimum_seconds 60 Minimum seconds between engine cycle starts.
claude_code_initial_model claude-sonnet-4-6 Claude model used during initial implementation.
claude_code_review_model claude-opus-4-8 Claude model used during self-review.
claude_code_initial_effort high Reasoning effort for initial implementation.
claude_code_review_effort high Reasoning effort for self-review.
claude_describe_model claude-haiku-4-5-20251001 Claude model used to write the final PR description before merge. A faster model is appropriate here.
claude_timeout_seconds 10800 (3 h) How long one Claude run may take before it is killed. Size it to the scope of your issues β€” generation is server-side, so a large implementation takes the time it takes regardless of your machine. A run cut short is salvaged as an incomplete draft PR rather than discarded.
dashboard_port 3000 HTTP port for the single shared Dashboard server.
dashboard_title Outright Mental Title displayed in the Dashboard header.
github_api_base_url https://api.github.com GitHub REST API base URL, for GitHub Enterprise.
github_api_version 2022-11-28 Value of the X-GitHub-Api-Version request header.

Per-project keys (each entry under projects)

Per-project values override the global ones for that project.

Key Default Purpose
github_repository β€” Required. Repository slug in owner/repo form.
github_token_name default token Name of the github_tokens entry to use for this project.
max_concurrency global max_concurrency Cap on how many of the shared cylinders may work this project at once. Never exceeds the global pool.
github_project_number β€” GitHub Projects v2 board number. Enables Project SDLC for this project.
reviewers [] GitHub logins to request review from (Project SDLC only).
focus_mode false When true, only issues labelled focus (and the PRs that advance them) are worked.
claude_code_initial_model / claude_code_review_model global value Per-project model overrides.
claude_code_initial_effort / claude_code_review_effort global value Per-project effort overrides.
claude_describe_model global value Per-project override for the final-description model.
claude_timeout_seconds global value Per-project override of the per-run timeout.
cycle_minimum_seconds global value Per-project override of the cycle minimum.
session_store_path <cwd>/.yoke/<owner>-<repo>-sessions.json Path for persisted local agent-session state.

CLI flags

These are the only command-line options; there are no positional arguments.

Flag Purpose
--once Run a single iteration, then exit.
--dry-run Print the plan without executing any Claude or GitHub actions.
--no-browser Start the Dashboard server but do not auto-open a browser window.

Focus mode is a per-project setting, not a flag: set focus_mode: true on a project in env.yaml and Yoke will pick up only issues labelled focus in that repository, plus the PRs that advance them.

Issue language the loop understands

Use normal GitHub issues, plus lightweight relationship phrases in the issue body:

blocked by #12
depends on #12
blocks #34

yoke will not start an issue while any referenced blocker remains open. Older eligible issues start first (bugs first, then milestone order, then creation time), up to max_concurrency.

The manual label

Apply the manual label to any issue or PR to remove it from automated work:

  • Issues labeled manual are never picked up by Yoke.
  • PRs labeled manual receive no automated actions (no self-review, no conflict resolution, no auto-merge) and do not count against max_concurrency.

Yoke creates the manual label in the repository on startup if it does not already exist.

The review label

Apply the review label to an issue to opt it into the implement-then-wait-for-review workflow (outrightmental.com#409): Yoke implements the issue, self-reviews, and fixes failing checks exactly as usual β€” but after the clean self-review it marks the PR ready and stops. The final PR is never squash-merged; it waits for a human. This is the flag that automated intake (e.g. Mailbot's note-to-issue pipeline) applies so a one-line emailed note becomes a reviewable PR without ever landing on main unattended.

  • Works in every mode; in Project SDLC it is redundant (that mode already never auto-merges).
  • The label is copied onto the PR when it is opened, so the no-merge gate survives even if the issue is closed or relabelled mid-flight.
  • A parked PR does not count against max_concurrency.
  • Converting the PR back to draft, or commenting on it, re-queues it: Yoke addresses the feedback, self-reviews, and requests review again.
  • Removing the label from both the issue and the PR returns the PR to the normal auto-merge flow.

Yoke creates the review label in the repository on startup if it does not already exist.

Milestone ordering

Milestones act as a priority queue, not a gate. Issues in earlier milestones are preferred over later-milestone issues, but all eligible unblocked issues can start regardless of milestone. Bug-typed issues (GitHub's native Issue Type = "Bug") always jump ahead of every other type.

Priority order: Bug > earlier milestone > later milestone > no milestone, then by creation time within each tier.

Parent and sub-issues

GitHub sub-issues are understood natively. A parent issue is automatically blocked until all of its open sub-issues are resolved β€” no explicit dependency phrases needed.

Development

npm test
npm run build

The Dashboard's keep-awake loop (src/dashboard/assets/keep-awake.{webm,mp4}) is committed, so a normal build needs nothing extra. To change how the lamp looks, edit and re-run tools/generate-keep-awake-video.py β€” it needs Pillow and GStreamer, neither of which Yoke itself depends on β€” then commit the regenerated files.

Status

This project is intentionally small and sharp: a local orchestrator, a GitHub client, a session store, a Claude agent client, and a planning engine. It is early infrastructure for people who want to run software projects as agentic systems instead of manually copying prompts between tabs.

About

Yoke a team of coding agents to work through a GitHub issue queue

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages