{"slug": "l3-simulator-agent-orchestrator-over-gh-issues", "title": "L3 Simulator: agent orchestrator over GH issues", "summary": "L3 Simulator is an open-source tool that orchestrates AI coding agents over GitHub issues, allowing developers to label issues for automated planning, coding, and PR creation while retaining human review and merge control. The tool uses GitHub labels as its source of truth and supports multi-repo dependencies, plan approval workflows, and automated retries on failure.", "body_md": "Have you ever wanted a tireless junior engineer who never sleeps or doom-scrolls?\nL3 Simulator orchestrates AI coding agents over your GitHub issues: label an issue\n`ready`\n\nand your synthetic L3 claims it, optionally writes up a plan,\ndispatches a coding agent (Claude Code or Codex) in an isolated git worktree,\nopens a PR, and babysits it through CI failures, review comments, and merge\nconflicts. In the meantime you go do something befitting your seniority.\n\n**L3 Simulator never merges anything. You are still responsible for code review and merging.** The `gh`\n\nwrapper hard-rejects merge commands, agents are\ndenied `gh`\n\nand `git push`\n\n, and CI greps for regressions. Your L3s can open all\nthe PRs they like, but shipping is above their pay grade.\n\n- Node >= 22\nauthenticated (`gh`\n\n`gh auth login`\n\n)`claude`\n\n(Claude Code) and/or`codex`\n\nCLI installed- A local clone of the repo you want to orchestrate\n\nInstall the CLI globally from npm:\n\n```\nnpm install -g l3-simulator\n```\n\nThis puts an `l3-simulator`\n\ncommand on your PATH. Prefer not to install\nglobally? `npx l3-simulator <command>`\n\nworks too. To build from source, see\n[Development](#development).\n\nThen, inside each repo you want to orchestrate:\n\n```\nl3-simulator init\n```\n\n`init`\n\nwrites `.l3-simulator.json`\n\ninto the repo (gitignore it; it contains a\nlocal path) and creates the five workflow labels on GitHub. Any command run\nfrom inside that repo picks up its config, so\nmultiple repos each get their own daemon, worktrees, state, and logs.\n\nGitHub labels are the orchestrator's source of truth: the issues UI is the dashboard, and a crash or restart re-derives everything from labels + PRs. Your L3s have no memory of their own. You can interact either via the TUI or via the GitHub issues UI.\n\n| Label | Meaning | Who sets it |\n|---|---|---|\n`ready` |\nStart working on this | you |\n`needs-plan` |\nPlan first; reply `#approve` to start coding, anything else revises the plan |\nyou |\n`needs-plan-noreview` |\nPlan first, then code immediately without waiting for approval | you |\n`in-progress` |\nClaimed; an agent is (or will be) working | orchestrator |\n`blocked` |\nNeeds your input (plan approval, questions, failures) | orchestrator |\n`in-review` |\nAn agent PR is open. Review and merge it yourself | orchestrator |\n`failure` |\nAdded with `blocked` when an agent actually failed. Urgent, reply to retry |\norchestrator |\n\nLifecycle:\n\n- Create issue with\n`ready`\n\ntag. Optionally add`needs-plan`\n\nor`needs-plan-noreview`\n\n- L3 claims an issue (adds\n`in-progress`\n\ntag) - (Optional) L3 plans work and posts a comment with the plan, waits for approval\n- Coding L3 starts in a worktree on\n`l3/issue-<n>-<slug>`\n\n- Submits a PR (tags\n`in-review`\n\n) - (Optional) comment on the PR to request updates\n- You merge, and the labels are removed\n\n**Cross-repo prerequisites file themselves.** When a coding agent concludes\nthe work is blocked on changes in another repository, it describes the\nissue(s) to file there, the orchestrator creates them and\nthe original issue automatically resumes once every prerequisite issue is\nclosed. You can run another instance of L3 Simulator on that repo as well.\n\n**Plans are approved explicitly.** When a plan is posted (`needs-plan`\n\n), the\nissue blocks until you reply. A reply containing `#approve`\n\nstarts coding —\nanything else in that reply is passed to the coding agent as guidance. Any\nother reply is treated as plan feedback: the plan agent produces a revised\nplan and re-posts it for review, as many rounds as you like.\n\n**Everything else blocked resumes on a reply.** Planning-agent questions,\nagent failures, and the fix-attempt cap all add `blocked`\n\nplus an explanatory\ncomment. Replying with any comment unblocks the issue and work resumes with\nyour reply as context. Removing the `blocked`\n\nlabel by hand also works. Human comments are passed\nto the coding agent and take precedence over the plan.\n\n**Multi-stage issues.** When a coding agent reports its PR is a first slice\n(`done: false`\n\n), the issue keeps `in-progress`\n\nalongside `in-review`\n\n, and the\nnext stage starts only after you merge the current PR.\n\nWhile a PR is open the orchestrator polls its health: failing checks, new\nreview comments, or merge conflicts dispatch a fix agent into the same\nworktree (capped at `maxFixIterationsPerPr`\n\nper PR; pushing your own commit to\nthe branch resets the counter). Branches that fall behind the base are\nupdated automatically, and merge conflicts are resolved even while an issue\nis blocked on something else.\n\nClosed issues are tidied automatically: workflow labels are stripped (so the\nclosed list looks clean) and worktrees whose PRs are merged or closed are\nremoved once the issue is no longer active — worktrees with uncommitted\nchanges are left for `l3-simulator cleanup --force`\n\n.\n\n```\nl3-simulator start                 # TUI dashboard + daemon (q to quit)\nl3-simulator start --headless      # plain log lines (cron, background)\nl3-simulator start --dry-run       # log every mutation instead of doing it\nl3-simulator run-once              # single tick (demos, cron)\nl3-simulator status                # one-shot table derived from GitHub\nl3-simulator cleanup               # prune worktrees for merged/closed PRs\n```\n\n`start`\n\nopens a live TUI: every workflow issue with its phase (queued, planning, coding,\nfixing, plan review, blocked, in review, next stage), the running agent's\nrole + elapsed time + live progress line (on the selected row), the open PR\nwith a CI glyph (✓ ✗ ●), the worktree branch, and a \"recently completed\"\nsection of closed issues the orchestrator shipped. A log pane shows recent\nevents (`--verbose`\n\nadds per-tool agent progress).\n\nKeys: `↑↓`\n\n/`j k`\n\nselect · `n`\n\nfile a new issue (title, body, label choice —\nappears on the next tick) · `r`\n\nforce a poll now · `o`\n\nopen a new **tmux pane**\ncd'd into the selected issue's worktree · `⏎`\n\nopen the PR (or issue) in your\nbrowser · `q`\n\n/ctrl-C graceful shutdown (kills agents, releases the lock).\n\nAgent transcripts land in\n`~/.local/share/l3-simulator/logs/<owner--repo>/issue-<n>/`\n\n.\n\nWhile a fix agent addresses PR feedback, the issue's `in-review`\n\nlabel swaps\nto `in-progress`\n\n(and back after the push) so GitHub also shows work is\nactive.\n\n`~/.config/l3-simulator/config.json`\n\n(override with `--config`\n\nor\n`L3_SIMULATOR_CONFIG`\n\n):\n\n```\n{\n  \"repo\": {\"path\": \"/home/you/myrepo\", \"slug\": \"you/myrepo\"},\n  \"maxConcurrentAgents\": 2,\n  \"pollIntervalSeconds\": 60,\n  \"maxFixIterationsPerPr\": 3,\n  \"agents\": {\n    \"plan\": {\"backend\": \"claude\", \"timeoutMinutes\": 15},\n    \"code\": {\"backend\": \"claude\", \"timeoutMinutes\": 60, \"model\": \"opus\"},\n    \"fix\": {\"backend\": \"codex\", \"timeoutMinutes\": 30},\n  },\n}\n```\n\nEach role (`plan`\n\n/`code`\n\n/`fix`\n\n) independently chooses `claude`\n\nor `codex`\n\n,\nplus optional `model`\n\n, `permissionMode`\n\n(Claude), and `extraArgs`\n\n. Use\n`init --global`\n\nfor a single per-user config instead\n(`~/.config/l3-simulator/config.json`\n\n). Resolution order:\n\n`--config`\n\nflag`L3_SIMULATOR_CONFIG`\n\n- nearest\n`.l3-simulator.json`\n\nup the directory tree - user config dir.\n\n**No merging.** The only`gh pr`\n\nsubcommands in the codebase are create/list/view/checks/comment/update-branch; the single`gh`\n\nchoke point (`src/github/gh.ts`\n\n) rejects`merge`\n\nargv outright.**Agents don't touch GitHub.** Claude runs with`gh`\n\nand`git push`\n\ndisallowed; Codex runs sandboxed. The orchestrator pushes branches and opens PRs itself, and only after verifying the agent actually committed.**Claude code/fix agents run with** inside their isolated worktree. Headless`bypassPermissions`\n\n`acceptEdits`\n\naborts on any shell command, so autonomous work is impossible without it. The`gh`\n\n/`git push`\n\ndeny rules are enforced even in bypass mode; set`agents.<role>.permissionMode`\n\nto tighten this if you prefer explicit allowlists.Anything the orchestrator can't handle is commented on the issue and handed to you. Your L3s know when to escalate to an adult.`blocked`\n\nis inert.- Recommended: branch protection on your default branch (require review, no force pushes). One daemon per repo (enforced by a pidfile).\n\n```\nnpm install              # install dependencies\nnpm run dev -- status    # run from source (tsx)\nnpm run demo             # render the sample dashboard (handy for screenshots)\nnpm test                 # vitest\nnpm run typecheck && npm run lint && npm run format:check\n```\n\nTo use a source build as the global `l3-simulator`\n\ncommand: `npm run build && npm link`\n\n.", "url": "https://wpnews.pro/news/l3-simulator-agent-orchestrator-over-gh-issues", "canonical_source": "https://github.com/czarandy/l3-simulator", "published_at": "2026-07-16 18:14:21+00:00", "updated_at": "2026-07-16 18:25:13.078285+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-tools"], "entities": ["L3 Simulator", "GitHub", "Claude Code", "Codex"], "alternates": {"html": "https://wpnews.pro/news/l3-simulator-agent-orchestrator-over-gh-issues", "markdown": "https://wpnews.pro/news/l3-simulator-agent-orchestrator-over-gh-issues.md", "text": "https://wpnews.pro/news/l3-simulator-agent-orchestrator-over-gh-issues.txt", "jsonld": "https://wpnews.pro/news/l3-simulator-agent-orchestrator-over-gh-issues.jsonld"}}