Explore the full workflow → Towards Autonomous Product Development
This MEGA Drop explains the workflow behind limen, including a live video walkthrough with Pi, Herdr, and Grok Bot. MEGA.dev shares practical articles, repos, and tools for working with AI.
You talk to one Pi coordinator. It starts workers and reviewers as real Pi processes in isolated Git worktrees. Each job leaves a branch, task, log, state, and session. You decide what to build and what to merge. The coordinator runs the harness.

Experimental. Commands, prompts, and project files may still change.
Requires macOS or Linux, Node.js 24+, Git, and pi on PATH. Windows is unsupported. Last known-good: pi 0.84.2, Herdr 0.8.0 — recorded on each job, not a runtime gate.
Jobs can live on an always-on seat (a VPS on Tailscale) while your laptop is only a window. See docs/remote.md. The walkthrough we actually ran is docs/vps.md.
When a job reaches a terminal state (done / failed / stopped), limen can POST {job, status, branch} to one or more destinations so a bot or routine can wake. Off by default. Installing limen does not enable any project.
Opt in per project with a private, git-excluded file:
# <project>/.limen/finish-webhook.env (mode 600; never commit)
LIMEN_FINISH_WEBHOOK_TARGETS='[{"url":"https://api2.cursor.sh/automations/webhook/…","auth":"Bearer …"}]'
Put that file beside the primary Git checkout (linked worktrees use the canonical root). Fresh spawns snapshot the path onto the job; continuations inherit the parent's choice. Home/legacy config does not opt a project into automation.
What “accepted” means. HTTP 2xx means the sender reached the endpoint. It does not mean a bot finished a turn — expect latency, and confirm the wake in the bot chat / routine run. Inspect with limen jobs <id> (finish-webhook-env, finish-webhook-attempt, finish-webhook).
Configuration uses only LIMEN_FINISH_WEBHOOK_* keys; retired bot-specific keys no longer work. Existing private files and launchers need the migration steps. The helper filename remains tony-finish-ping.sh.
Full setup, multi-target notes, deliberate retry, and troubleshooting: docs/finish-webhooks.md.
A spawned job runs pi --approve as you — or claude -p --permission-mode bypassPermissions when it runs on the Claude engine. A worktree and process group provide separation, not a security sandbox. A worker can do anything your account can do. Look at the branch before you merge. See SECURITY.md.
This part is yours. Once:
git clone https://github.com/overment/limen.git
cd limen
npm install
npm link
npm link puts that clone on PATH. The binary reads hook/ and templates/ next to itself. Projects do not copy those files. After git pull on the clone, /reload the coordinator.
Then in each project:
cd /path/to/your-project
limen init
pi --provider openai-codex --model gpt-6-astra --thinking xhigh
limen init plants what the project owns (vision, board, feature lanes, styleguide) and a stub that loads package hooks. It never overwrites existing project files. It always deletes leftover .pi/extensions/limen-*.ts hook copies so they cannot load beside the stub. limen init --drop-leftovers deletes only prompt copies that still match the package.
pi in that directory is the coordinator. From here you talk. You do not drive the job CLI.
Tell the coordinator the outcome you want. It writes or moves the ticket, keeps spec/build.md aligned, commits the ticket so the worker can see it, and starts a job. Stay in that conversation. A wake arrives when a job finishes. Ask only when something looks wrong, or when the coordinator asks you — product ambiguity, a real tradeoff, credentials, or a merge.
A useful ask names the outcome and the first artifact, not a tour of the repo. The coordinator turns that into a short spawn plus a Ticket: pointer. It does not paste the ticket into the prompt.
done means the run ended cleanly: Pi exited 0, or a hosted session ended, without a final error or aborted stop reason. A provider-errored run records failed with that reason. Neither state means the ticket is finished or the branch is safe to merge. The coordinator inspects the record, the diff, and the checks, then either merges, resumes a repair, or asks you.
When the blast radius earns a second pair of eyes, the coordinator starts a fresh reviewer against the candidate. The reviewer reports a verdict; it does not rewrite the branch. You still merge.
When the question is what to build rather than whether the branch is right — interface shape, feature specification, a second read on a decision — the coordinator can spawn an advisor on Claude instead of Pi: limen spawn --role advisor --engine claude --detached. It runs like any other detached job, in its own worktree, and its final message is a report the coordinator files. It advises; it never writes code, commits, or merges. A Claude job arrives with whatever MCP servers and skills your own claude install has, which is the point of reaching for it. It has no interactive tab and limen steer does not reach it.
These commands are the harness. The coordinator types them. They are here so you can recognize a job ID, a wake, or a recovery step — not as a daily script.
limen spawn --label "F001 auth handler" \
"Implement F001's session-handler slice. Start with the failing session test. One commit. Ticket: spec/features/active/F001-auth/ticket.md"
limen jobs
limen jobs <id|suffix|label>
git diff HEAD...<branch>
limen spawn --review --branch limen/<job-id> --label "F001 auth review" \
"Review the F001 candidate against spec/features/active/F001-auth/ticket.md. Name the commit reviewed."
limen steer <id> "stay on the session test; do not widen"
limen stop <id> "reason"
limen spawn --branch limen/<job-id> "Focused resume instruction"
The last line of spawn is the durable job ID. A running job picks up a steer between tool calls. Stop sends TERM, then escalates. Resume reuses the branch and its worktree, uncommitted files included. The coordinator inspects that state first.
Finished jobs keep their files under .limen/jobs/. Extra checkouts do not stay: the next spawn drops finished worktrees, and limen prune does the same on demand. Resume with --branch keeps that checkout.
To keep a finished Pi job's conversation, run limen continue <job-id> "Follow-up instruction". If its checkout was pruned, Limen restores the recorded path from the surviving local branch and copies the saved session into a new linked job. Only committed branch contents return; pruned uncommitted files are lost. A missing branch or transcript prevents recovery, and a branch checked out elsewhere is not taken over.
A job is bounded by 90 minutes (--timeout 20m) and 900 tool-start events (LIMEN_MAX_TOOL_CALLS). A bound records failed; it does not finish the ticket. Do not run limen wait in the coordinator conversation — it blocks you.
For collaborators sharing a project, limen ticket-author spec/features/active/F001-auth/ticket.md reports the name, email, and commit that first added the ticket, following Git-recognized renames between lanes. Paths are relative to the current directory; absolute paths within the repository also work. The lookup reads the current branch's committed HEAD, never the current operator's Git config or GitHub session, and writes nothing.
A GitHub noreply email also yields its recorded login. Ordinary emails remain usable identities without a GitHub account or network access. This is the creation author, not the committer or latest editor; it is evidence from Git, not verified human identity. Shared bot credentials identify the bot, not the person who asked it to file the ticket. Keep distinct authors on filing commits when collaborators need distinct attribution.
Uncommitted paths and shallow history report authorship unavailable rather than guessing. Commit a new ticket before looking it up; fetch complete history for a shallow clone. A move Git cannot recognize, a squash, or rewritten history can lose original attribution. Deleting and recreating a path starts a new ticket history. No author tags are added to ticket Markdown.
Coordinators use openai-codex/gpt-6-astra with xhigh thinking. In an existing Herdr pane at a shell prompt, with the project as its working directory, Tony starts a peer with explicit Pi arguments:
herdr agent start limen-peer --kind pi --pane <pane-id> -- \
--provider openai-codex --model gpt-6-astra --thinking xhigh
Herdr forwards the arguments after --; it does not select Limen's model. This repository also tracks .pi/settings.json with coordinator defaults for a bare pi launch when the project is trusted. Explicit flags work even when project settings are ignored. limen init does not change other projects' Pi settings. Adam's global ~/.pi/agent/settings.json is left untouched; its grok-4.6 default still applies outside this override.
Pi workers default to openai-codex/gpt-6-astra:high in Limen's spawn path, for both hosted and detached jobs. No export is required. To make the policy explicit, set this in each coordinator's shell environment before starting it:
export LIMEN_WORKER_MODEL="openai-codex/gpt-6-astra:high"
When the worker must receive literal Pi arguments, pass all three through Limen:
limen spawn --tab --engine pi \
--provider openai-codex --model gpt-6-astra --thinking high \
--label "short worker task" 'Implement the requested slice.'
--provider, --model, and --thinking reach Pi as separate flag/value pairs in both hosted and detached mode; use --detached instead of --tab for a background worker. limen continue accepts the same three flags. The existing combined --model openai-codex/gpt-6-astra:high form remains supported. Explicit --thinking takes precedence over a combined selector's thinking suffix in Pi. Provider and thinking flags are Pi-only; a Claude spawn rejects them before creating a job.
Precedence is --model, then LIMEN_WORKER_MODEL, then the package default. A requested --review uses LIMEN_REVIEWER_MODEL instead of LIMEN_WORKER_MODEL, with the same package fallback; neither variable starts a review. Adam performs reviews for Alice/Limen work, so do not spawn an independent reviewer unless asked. If requested, its model can be explicit or set with export LIMEN_REVIEWER_MODEL="openai-codex/gpt-6-astra:high".
The --engine claude path keeps its own CLI default unless --model is supplied. Pi's PI_PROVIDER, PI_MODEL, and PI_REASONING_LEVEL describe the current session; they do not configure a child Pi launch.
A non-Git parent can hold several independent Git children. You initialize once at the parent (limen workspace init) and map the children in spec/workspace.md. After that, tell the coordinator which repo the work belongs in. It passes exactly one --repo per job. Tickets stay under the parent; branches, worktrees, diffs, and review stay in the selected child.
The installed limen is the default shop manual, role prompts, speech register, and hooks. limen init only creates what the project owns:
.agents/limen/styleguide.md project coding practice
spec/vision.md durable product intent
spec/build.md TRACK / NOW / NEXT / PROVEN
spec/features/ planned, active, done, and dropped work
.pi/extensions/limen.ts stub: load hooks from the package
.limen/jobs/<id>/ runtime evidence
Optional overlays replace a package default for that file only: AGENTS.md, .agents/limen/worker.md, .agents/limen/reviewer.md, .agents/limen/communication.md. A file that still matches the package is a leftover copy; the coordinator names it. Different bytes are an overlay — keep, drop, or edit. Never overwrite an overlay.
The communication hook puts the shop manual, speech register, vision, and styleguide on the system prompt once per model call (board digest last, so a NOW/NEXT change does not break the cached prefix). A short per-turn note names the audience and the reply rules; a wake cue lives there, not in the system prompt. After a write or edit, the tool result recalls the rule that applies. Updating the clone updates every project on that machine.
If the optional speak CLI is executable on PATH, Pi also gets a /speak command. It reads the latest assistant response as a compressed spoken brief without starting another model turn; /speak full skips compression. The command is absent when the CLI is unavailable, and Limen never starts audio on its own.
The coordinator does this. You only need it if you are looking at a stuck tab yourself.
| Symptom | Safe next step |
|---|---|
| Quiet or repetitive job | limen jobs <id>, the log, and the worktree. Stop only on evidence, then resume narrower. |
| Worker has a real question | Read its durable note, answer it, resume the branch. |
Wrapper is dead but state says running | Verify the recorded PID, correct the plain state file, then resume. |
| Completion wake was missed | Inspect .limen/jobs/ and Git. Job files remain canonical if a notification is missed. |
limen init
limen init --drop-leftovers
limen workspace init
limen spawn "instruction" [--label L] [--provider P] [--model M] [--thinking T] [--branch B] [--role NAME] [--engine pi|claude] [--timeout 20m] [--task-file F|-] [--prepare CMD]
limen spawn --repo R "instruction" [--label L] [--model M]
limen spawn --review --branch B --label L "instruction"
limen jobs [--running|--active|--all|<id|suffix|label>]
limen diff <id|suffix|label>
limen prune
limen steer <id|suffix|label> | --running "correction"
limen stop <id|suffix|label> [reason]
limen wait <id|suffix|label>
limen watch <id|suffix|label> | --running
limen unwatch <id|suffix|label> | --all
limen open <id|suffix|label>
limen close <FNNN>
IDs, unique suffixes, and unique labels are interchangeable where shown.
At a terminal, jobs renders an aligned table for eyes; piped, it prints the compact format tools parse. LIMEN_VIEW=human|compact forces a view; NO_COLOR drops the paint.
See CONTRIBUTING.md. CI runs the same checks on Linux and macOS:
npm run check
Limen has zero runtime dependencies. Capability belongs in src/; operating judgment belongs in templates and project files.
444 commits
TypeScript
90.1%
HTML
7.8%
Shell
1.2%
Explore the full workflow → Towards Autonomous Product Development
This MEGA Drop explains the workflow behind limen, including a live video walkthrough with Pi, Herdr, and Grok Bot. MEGA.dev shares practical articles, repos, and tools for working with AI.
You talk to one Pi coordinator. It starts workers and reviewers as real Pi processes in isolated Git worktrees. Each job leaves a branch, task, log, state, and session. You decide what to build and what to merge. The coordinator runs the harness.

Experimental. Commands, prompts, and project files may still change.
Requires macOS or Linux, Node.js 24+, Git, and pi on PATH. Windows is unsupported. Last known-good: pi 0.84.2, Herdr 0.8.0 — recorded on each job, not a runtime gate.
Jobs can live on an always-on seat (a VPS on Tailscale) while your laptop is only a window. See docs/remote.md. The walkthrough we actually ran is docs/vps.md.
When a job reaches a terminal state (done / failed / stopped), limen can POST {job, status, branch} to one or more destinations so a bot or routine can wake. Off by default. Installing limen does not enable any project.
Opt in per project with a private, git-excluded file:
# <project>/.limen/finish-webhook.env (mode 600; never commit)
LIMEN_FINISH_WEBHOOK_TARGETS='[{"url":"https://api2.cursor.sh/automations/webhook/…","auth":"Bearer …"}]'
Put that file beside the primary Git checkout (linked worktrees use the canonical root). Fresh spawns snapshot the path onto the job; continuations inherit the parent's choice. Home/legacy config does not opt a project into automation.
What “accepted” means. HTTP 2xx means the sender reached the endpoint. It does not mean a bot finished a turn — expect latency, and confirm the wake in the bot chat / routine run. Inspect with limen jobs <id> (finish-webhook-env, finish-webhook-attempt, finish-webhook).
Configuration uses only LIMEN_FINISH_WEBHOOK_* keys; retired bot-specific keys no longer work. Existing private files and launchers need the migration steps. The helper filename remains tony-finish-ping.sh.
Full setup, multi-target notes, deliberate retry, and troubleshooting: docs/finish-webhooks.md.
A spawned job runs pi --approve as you — or claude -p --permission-mode bypassPermissions when it runs on the Claude engine. A worktree and process group provide separation, not a security sandbox. A worker can do anything your account can do. Look at the branch before you merge. See SECURITY.md.
This part is yours. Once:
git clone https://github.com/overment/limen.git
cd limen
npm install
npm link
npm link puts that clone on PATH. The binary reads hook/ and templates/ next to itself. Projects do not copy those files. After git pull on the clone, /reload the coordinator.
Then in each project:
cd /path/to/your-project
limen init
pi --provider openai-codex --model gpt-6-astra --thinking xhigh
limen init plants what the project owns (vision, board, feature lanes, styleguide) and a stub that loads package hooks. It never overwrites existing project files. It always deletes leftover .pi/extensions/limen-*.ts hook copies so they cannot load beside the stub. limen init --drop-leftovers deletes only prompt copies that still match the package.
pi in that directory is the coordinator. From here you talk. You do not drive the job CLI.
Tell the coordinator the outcome you want. It writes or moves the ticket, keeps spec/build.md aligned, commits the ticket so the worker can see it, and starts a job. Stay in that conversation. A wake arrives when a job finishes. Ask only when something looks wrong, or when the coordinator asks you — product ambiguity, a real tradeoff, credentials, or a merge.
A useful ask names the outcome and the first artifact, not a tour of the repo. The coordinator turns that into a short spawn plus a Ticket: pointer. It does not paste the ticket into the prompt.
done means the run ended cleanly: Pi exited 0, or a hosted session ended, without a final error or aborted stop reason. A provider-errored run records failed with that reason. Neither state means the ticket is finished or the branch is safe to merge. The coordinator inspects the record, the diff, and the checks, then either merges, resumes a repair, or asks you.
When the blast radius earns a second pair of eyes, the coordinator starts a fresh reviewer against the candidate. The reviewer reports a verdict; it does not rewrite the branch. You still merge.
When the question is what to build rather than whether the branch is right — interface shape, feature specification, a second read on a decision — the coordinator can spawn an advisor on Claude instead of Pi: limen spawn --role advisor --engine claude --detached. It runs like any other detached job, in its own worktree, and its final message is a report the coordinator files. It advises; it never writes code, commits, or merges. A Claude job arrives with whatever MCP servers and skills your own claude install has, which is the point of reaching for it. It has no interactive tab and limen steer does not reach it.
These commands are the harness. The coordinator types them. They are here so you can recognize a job ID, a wake, or a recovery step — not as a daily script.
limen spawn --label "F001 auth handler" \
"Implement F001's session-handler slice. Start with the failing session test. One commit. Ticket: spec/features/active/F001-auth/ticket.md"
limen jobs
limen jobs <id|suffix|label>
git diff HEAD...<branch>
limen spawn --review --branch limen/<job-id> --label "F001 auth review" \
"Review the F001 candidate against spec/features/active/F001-auth/ticket.md. Name the commit reviewed."
limen steer <id> "stay on the session test; do not widen"
limen stop <id> "reason"
limen spawn --branch limen/<job-id> "Focused resume instruction"
The last line of spawn is the durable job ID. A running job picks up a steer between tool calls. Stop sends TERM, then escalates. Resume reuses the branch and its worktree, uncommitted files included. The coordinator inspects that state first.
Finished jobs keep their files under .limen/jobs/. Extra checkouts do not stay: the next spawn drops finished worktrees, and limen prune does the same on demand. Resume with --branch keeps that checkout.
To keep a finished Pi job's conversation, run limen continue <job-id> "Follow-up instruction". If its checkout was pruned, Limen restores the recorded path from the surviving local branch and copies the saved session into a new linked job. Only committed branch contents return; pruned uncommitted files are lost. A missing branch or transcript prevents recovery, and a branch checked out elsewhere is not taken over.
A job is bounded by 90 minutes (--timeout 20m) and 900 tool-start events (LIMEN_MAX_TOOL_CALLS). A bound records failed; it does not finish the ticket. Do not run limen wait in the coordinator conversation — it blocks you.
For collaborators sharing a project, limen ticket-author spec/features/active/F001-auth/ticket.md reports the name, email, and commit that first added the ticket, following Git-recognized renames between lanes. Paths are relative to the current directory; absolute paths within the repository also work. The lookup reads the current branch's committed HEAD, never the current operator's Git config or GitHub session, and writes nothing.
A GitHub noreply email also yields its recorded login. Ordinary emails remain usable identities without a GitHub account or network access. This is the creation author, not the committer or latest editor; it is evidence from Git, not verified human identity. Shared bot credentials identify the bot, not the person who asked it to file the ticket. Keep distinct authors on filing commits when collaborators need distinct attribution.
Uncommitted paths and shallow history report authorship unavailable rather than guessing. Commit a new ticket before looking it up; fetch complete history for a shallow clone. A move Git cannot recognize, a squash, or rewritten history can lose original attribution. Deleting and recreating a path starts a new ticket history. No author tags are added to ticket Markdown.
Coordinators use openai-codex/gpt-6-astra with xhigh thinking. In an existing Herdr pane at a shell prompt, with the project as its working directory, Tony starts a peer with explicit Pi arguments:
herdr agent start limen-peer --kind pi --pane <pane-id> -- \
--provider openai-codex --model gpt-6-astra --thinking xhigh
Herdr forwards the arguments after --; it does not select Limen's model. This repository also tracks .pi/settings.json with coordinator defaults for a bare pi launch when the project is trusted. Explicit flags work even when project settings are ignored. limen init does not change other projects' Pi settings. Adam's global ~/.pi/agent/settings.json is left untouched; its grok-4.6 default still applies outside this override.
Pi workers default to openai-codex/gpt-6-astra:high in Limen's spawn path, for both hosted and detached jobs. No export is required. To make the policy explicit, set this in each coordinator's shell environment before starting it:
export LIMEN_WORKER_MODEL="openai-codex/gpt-6-astra:high"
When the worker must receive literal Pi arguments, pass all three through Limen:
limen spawn --tab --engine pi \
--provider openai-codex --model gpt-6-astra --thinking high \
--label "short worker task" 'Implement the requested slice.'
--provider, --model, and --thinking reach Pi as separate flag/value pairs in both hosted and detached mode; use --detached instead of --tab for a background worker. limen continue accepts the same three flags. The existing combined --model openai-codex/gpt-6-astra:high form remains supported. Explicit --thinking takes precedence over a combined selector's thinking suffix in Pi. Provider and thinking flags are Pi-only; a Claude spawn rejects them before creating a job.
Precedence is --model, then LIMEN_WORKER_MODEL, then the package default. A requested --review uses LIMEN_REVIEWER_MODEL instead of LIMEN_WORKER_MODEL, with the same package fallback; neither variable starts a review. Adam performs reviews for Alice/Limen work, so do not spawn an independent reviewer unless asked. If requested, its model can be explicit or set with export LIMEN_REVIEWER_MODEL="openai-codex/gpt-6-astra:high".
The --engine claude path keeps its own CLI default unless --model is supplied. Pi's PI_PROVIDER, PI_MODEL, and PI_REASONING_LEVEL describe the current session; they do not configure a child Pi launch.
A non-Git parent can hold several independent Git children. You initialize once at the parent (limen workspace init) and map the children in spec/workspace.md. After that, tell the coordinator which repo the work belongs in. It passes exactly one --repo per job. Tickets stay under the parent; branches, worktrees, diffs, and review stay in the selected child.
The installed limen is the default shop manual, role prompts, speech register, and hooks. limen init only creates what the project owns:
.agents/limen/styleguide.md project coding practice
spec/vision.md durable product intent
spec/build.md TRACK / NOW / NEXT / PROVEN
spec/features/ planned, active, done, and dropped work
.pi/extensions/limen.ts stub: load hooks from the package
.limen/jobs/<id>/ runtime evidence
Optional overlays replace a package default for that file only: AGENTS.md, .agents/limen/worker.md, .agents/limen/reviewer.md, .agents/limen/communication.md. A file that still matches the package is a leftover copy; the coordinator names it. Different bytes are an overlay — keep, drop, or edit. Never overwrite an overlay.
The communication hook puts the shop manual, speech register, vision, and styleguide on the system prompt once per model call (board digest last, so a NOW/NEXT change does not break the cached prefix). A short per-turn note names the audience and the reply rules; a wake cue lives there, not in the system prompt. After a write or edit, the tool result recalls the rule that applies. Updating the clone updates every project on that machine.
If the optional speak CLI is executable on PATH, Pi also gets a /speak command. It reads the latest assistant response as a compressed spoken brief without starting another model turn; /speak full skips compression. The command is absent when the CLI is unavailable, and Limen never starts audio on its own.
The coordinator does this. You only need it if you are looking at a stuck tab yourself.
| Symptom | Safe next step |
|---|---|
| Quiet or repetitive job | limen jobs <id>, the log, and the worktree. Stop only on evidence, then resume narrower. |
| Worker has a real question | Read its durable note, answer it, resume the branch. |
Wrapper is dead but state says running | Verify the recorded PID, correct the plain state file, then resume. |
| Completion wake was missed | Inspect .limen/jobs/ and Git. Job files remain canonical if a notification is missed. |
limen init
limen init --drop-leftovers
limen workspace init
limen spawn "instruction" [--label L] [--provider P] [--model M] [--thinking T] [--branch B] [--role NAME] [--engine pi|claude] [--timeout 20m] [--task-file F|-] [--prepare CMD]
limen spawn --repo R "instruction" [--label L] [--model M]
limen spawn --review --branch B --label L "instruction"
limen jobs [--running|--active|--all|<id|suffix|label>]
limen diff <id|suffix|label>
limen prune
limen steer <id|suffix|label> | --running "correction"
limen stop <id|suffix|label> [reason]
limen wait <id|suffix|label>
limen watch <id|suffix|label> | --running
limen unwatch <id|suffix|label> | --all
limen open <id|suffix|label>
limen close <FNNN>
IDs, unique suffixes, and unique labels are interchangeable where shown.
At a terminal, jobs renders an aligned table for eyes; piped, it prints the compact format tools parse. LIMEN_VIEW=human|compact forces a view; NO_COLOR drops the paint.
See CONTRIBUTING.md. CI runs the same checks on Linux and macOS:
npm run check
Limen has zero runtime dependencies. Capability belongs in src/; operating judgment belongs in templates and project files.
444 commits
TypeScript
90.1%
HTML
7.8%
Shell
1.2%