l3-simulator
v1.0.1
Published
GitHub issue orchestrator that dispatches AI coding agents ("L3s") over labeled issues: plan, code, and fix in isolated git worktrees, open PRs, and babysit CI and review feedback. Humans merge.
Maintainers
Readme
L3 Simulator
Have you ever wanted a tireless junior engineer who never sleeps or doom-scrolls?
L3 Simulator orchestrates AI coding agents over your GitHub issues: label an issue
ready and your synthetic L3 claims it, optionally writes up a plan,
dispatches a coding agent (Claude Code or Codex) in an isolated git worktree,
opens a PR, and babysits it through CI failures, review comments, and merge
conflicts. In the meantime you go do something befitting your seniority.
L3 Simulator never merges anything. You are still responsible for code review and merging. The gh wrapper hard-rejects merge commands, agents are
denied gh and git push, and CI greps for regressions. Your L3s can open all
the PRs they like, but shipping is above their pay grade.
Requirements
- Node >= 22
ghauthenticated (gh auth login)claude(Claude Code) and/orcodexCLI installed- A local clone of the repo you want to orchestrate
Installation
Install the CLI globally from npm:
npm install -g l3-simulatorThis puts an l3-simulator command on your PATH. Prefer not to install
globally? npx l3-simulator <command> works too. To build from source, see
Development.
Then, inside each repo you want to orchestrate:
l3-simulator initinit writes .l3-simulator.json into the repo (gitignore it; it contains a
local path) and creates the five workflow labels on GitHub. Any command run
from inside that repo picks up its config, so
multiple repos each get their own daemon, worktrees, state, and logs.
The label workflow
GitHub 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.
| Label | Meaning | Who sets it |
| --------------------- | ----------------------------------------------------------------------------- | ------------ |
| ready | Start working on this | you |
| needs-plan | Plan first; reply #approve to start coding, anything else revises the plan | you |
| needs-plan-noreview | Plan first, then code immediately without waiting for approval | you |
| in-progress | Claimed; an agent is (or will be) working | orchestrator |
| blocked | Needs your input (plan approval, questions, failures) | orchestrator |
| in-review | An agent PR is open. Review and merge it yourself | orchestrator |
| failure | Added with blocked when an agent actually failed. Urgent, reply to retry | orchestrator |
Lifecycle:
- Create issue with
readytag. Optionally addneeds-planorneeds-plan-noreview - L3 claims an issue (adds
in-progresstag) - (Optional) L3 plans work and posts a comment with the plan, waits for approval
- Coding L3 starts in a worktree on
l3/issue-<n>-<slug> - Submits a PR (tags
in-review) - (Optional) comment on the PR to request updates
- You merge, and the labels are removed
Cross-repo prerequisites file themselves. When a coding agent concludes the work is blocked on changes in another repository, it describes the issue(s) to file there, the orchestrator creates them and the original issue automatically resumes once every prerequisite issue is closed. You can run another instance of L3 Simulator on that repo as well.
Plans are approved explicitly. When a plan is posted (needs-plan), the
issue blocks until you reply. A reply containing #approve starts coding —
anything else in that reply is passed to the coding agent as guidance. Any
other reply is treated as plan feedback: the plan agent produces a revised
plan and re-posts it for review, as many rounds as you like.
Everything else blocked resumes on a reply. Planning-agent questions,
agent failures, and the fix-attempt cap all add blocked plus an explanatory
comment. Replying with any comment unblocks the issue and work resumes with
your reply as context. Removing the blocked label by hand also works. Human comments are passed
to the coding agent and take precedence over the plan.
Multi-stage issues. When a coding agent reports its PR is a first slice
(done: false), the issue keeps in-progress alongside in-review, and the
next stage starts only after you merge the current PR.
While a PR is open the orchestrator polls its health: failing checks, new
review comments, or merge conflicts dispatch a fix agent into the same
worktree (capped at maxFixIterationsPerPr per PR; pushing your own commit to
the branch resets the counter). Branches that fall behind the base are
updated automatically, and merge conflicts are resolved even while an issue
is blocked on something else.
Closed issues are tidied automatically: workflow labels are stripped (so the
closed list looks clean) and worktrees whose PRs are merged or closed are
removed once the issue is no longer active — worktrees with uncommitted
changes are left for l3-simulator cleanup --force.
Usage
l3-simulator start # TUI dashboard + daemon (q to quit)
l3-simulator start --headless # plain log lines (cron, background)
l3-simulator start --dry-run # log every mutation instead of doing it
l3-simulator run-once # single tick (demos, cron)
l3-simulator status # one-shot table derived from GitHub
l3-simulator cleanup # prune worktrees for merged/closed PRsThe dashboard
start opens a live TUI: every workflow issue with its phase (queued, planning, coding,
fixing, plan review, blocked, in review, next stage), the running agent's
role + elapsed time + live progress line (on the selected row), the open PR
with a CI glyph (✓ ✗ ●), the worktree branch, and a "recently completed"
section of closed issues the orchestrator shipped. A log pane shows recent
events (--verbose adds per-tool agent progress).
Keys: ↑↓/j k select · n file a new issue (title, body, label choice —
appears on the next tick) · r force a poll now · o open a new tmux pane
cd'd into the selected issue's worktree · ⏎ open the PR (or issue) in your
browser · q/ctrl-C graceful shutdown (kills agents, releases the lock).
Agent transcripts land in
~/.local/share/l3-simulator/logs/<owner--repo>/issue-<n>/.
While a fix agent addresses PR feedback, the issue's in-review label swaps
to in-progress (and back after the push) so GitHub also shows work is
active.
Configuration
~/.config/l3-simulator/config.json (override with --config or
L3_SIMULATOR_CONFIG):
{
"repo": {"path": "/home/you/myrepo", "slug": "you/myrepo"},
"maxConcurrentAgents": 2,
"pollIntervalSeconds": 60,
"maxFixIterationsPerPr": 3,
"agents": {
"plan": {"backend": "claude", "timeoutMinutes": 15},
"code": {"backend": "claude", "timeoutMinutes": 60, "model": "opus"},
"fix": {"backend": "codex", "timeoutMinutes": 30},
},
}Each role (plan/code/fix) independently chooses claude or codex,
plus optional model, permissionMode (Claude), and extraArgs. Use
init --global for a single per-user config instead
(~/.config/l3-simulator/config.json). Resolution order:
--configflagL3_SIMULATOR_CONFIG- nearest
.l3-simulator.jsonup the directory tree - user config dir.
Safety model
- No merging. The only
gh prsubcommands in the codebase are create/list/view/checks/comment/update-branch; the singleghchoke point (src/github/gh.ts) rejectsmergeargv outright. - Agents don't touch GitHub. Claude runs with
ghandgit pushdisallowed; 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
bypassPermissionsinside their isolated worktree. HeadlessacceptEditsaborts on any shell command, so autonomous work is impossible without it. Thegh/git pushdeny rules are enforced even in bypass mode; setagents.<role>.permissionModeto tighten this if you prefer explicit allowlists. blockedis inert. Anything the orchestrator can't handle is commented on the issue and handed to you. Your L3s know when to escalate to an adult.- Recommended: branch protection on your default branch (require review, no force pushes). One daemon per repo (enforced by a pidfile).
Development
npm install # install dependencies
npm run dev -- status # run from source (tsx)
npm run demo # render the sample dashboard (handy for screenshots)
npm test # vitest
npm run typecheck && npm run lint && npm run format:checkTo use a source build as the global l3-simulator command: npm run build &&
npm link.
