Agentilda
What is this?
This repo is a collection of installers (BASH and Ruby) which ensure a consistent vendor-neutral agentic setup on your computer, coupled with an agentic software team that diligently creates your feature specs in collaboration with you, and then works on them until they become reviewed, CI-passing PRs that you get to merge.
Here is a screenshot of agents working on two plans at the same time, but on two different phases of the process:
Note
-
agentilda(also known astildaexecutable) is the Ruby Gem, which is a CLI tool that creates and manages the.plansfolder, and comes with eight or so specialized agents that take a spec.md file and work through it until it's a set of PRs open, reviewed, and passing on your CI. It does not automatically merge anything. -
agentilda-ai-setupis the GitHub repo that's a mixture of BASH and Ruby installers. It's comes with theconfiguration.ymlfile, which lists the installation commands for the coding agents you'd like to install locally, any other executables you might want (for instance, it installsbt— braintrust's CLI utility), and then you can list any number of Github Repos and use them to install skills, plugins, commands from them, specifying exactly which you want to install and which you want to exclude. Moreover you can specify a sub-directory of a github repo to install from. - The final piece of the puzzle is the locking gem
agent-lock. This flexible gem comes with the executablealockand a skill teaching agents how to use it. Usingalockagents can work in parallel in the same worktree but on different files, sub-folders, and so on. The gem uses locally running Redis as the default backend, and if that's not available, it uses the file system. The choice of the backend happens once and is saved in the git-ignored file in your local working repo.
Together, the three repos, after installation provide you with the consistent way to replicate your ~/.agents and ~/.claude folders on multiple computers, and to enable a consistent agentic software team workflow across any number of many projects.
Agentilda turns a short feature description into a reviewed pull request. You write the brief; a team of seven Claude Code agents researches it, specifies it, plans it, builds it and reviews it. You merge.
Install
gem install agentilda agent-lock -N
hash -r
tilda --helpagent-lock (the alock command) stops agents sharing a checkout from overwriting each other's files.
Both commands have tab completion. Add this to ~/.zshrc (or use bash in ~/.bashrc):
eval "$(tilda completion zsh)"
eval "$(alock completion zsh)"To give your coding agents the bundled tilda skills, copy them into the shared skill store. There are seven: tilda (how the factory works, and which of the others to load), tilda-setup (put a project on tilda), tilda-spec (create a plan and write its spec), tilda-documents (the files in a plan folder and the formats tilda parses), tilda-run (tilda start and tilda run, clear blocks), tilda-status (where plans stand, the control room) and tilda-sync (folder names, PR titles, Linear).
tilda skills install # copies skills/* into ~/.agents/skills; -h lists them
tilda skills install -n # what it would install, keep or replaceIt copies rather than links, so re-run it with --overwrite after upgrading the gem.
Quick start
Run these anywhere inside your project:
tilda init # writes .tilda.yml and creates .plans/; asks on a terminal, -y asks nothing
tilda create tax rule dsl # creates .plans/000.00-⚪️ → tax-rule-dsl/spec.md and opens it
# finish the brief in spec.md, then save it
tilda run -n # dry run: shows which agent would take which plan
tilda start # the factory and its control room, up until Stop All
tilda list-plans # every plan, its state and its pull requestsThe agents stop at an approved pull request. Merging it is up to you.
Project settings: .tilda.yml
Every command that reads or writes plans first finds the project root and works from there, so it does not matter which subdirectory you type it in. The root is the nearest directory above you holding .tilda.yml or .plans/; failing that, the nearest holding .git. Your home directory counts only if it holds .tilda.yml, so a dotfiles repository there cannot capture every project under it. When the root is not where you are, one line on STDERR says so (-q silences it); when there is none, the command exits 66. -D and PLANS_DIR resolve against the root. version, states, docs, agents, describe, completion, eval and skills need no project and skip the search.
tilda init writes .tilda.yml at the nearest .git (else the current directory) and creates .plans/. It takes --project, --lane, --[no-]frontend and --jobs, asks on a terminal for whatever no flag gave, and confirms before writing. -y asks nothing, -n prints the file instead, and an existing file is kept unless --overwrite.
project: Equilibris App # heading for INDEX.md; default: the root's directory name
lane: full # default lane for `tilda create`: full | plan | quick
frontend: false # a spec's `frontend:` when spec.md says nothing; omit to let each plan decide
jobs: 4 # how many plans `tilda run` works on at once
commands: # option defaults for any command, by the name you type it as
run:
timeout: 1800
resync prs:
adopt: falseA typed flag beats the plan's spec.md frontmatter, which beats .tilda.yml, which beats the built-in default. jobs, lane and project are the same settings as commands.run.jobs, commands.create.lane and commands.index.project; set either spelling, not both. An unknown key, a bad value, or an option the command does not take is an error naming the file (exit 65): a setting silently ignored is a timeout that stops applying with no signal. dry_run cannot be a default. ~/.local/config/agentilda.json is no longer read; move its run section under commands:.
How it works
The folder name is the state
Every feature is one folder under .plans/, named NNN.MM-<emoji> → <slug>:
.plans/000.00-✅ → dev-foundation
001.00-🟡 → tenancy-households
001.01-🕰️ → schedule-k1-backfill # shipped first, documented afterwards
002.00-⚪️ → tax-rule-dsl
-
The number is set once and never changes. Branches, pull request titles and
pull-requests.mdall join on it.MMis00for a normal plan;01to99marks a retroactive plan slotted in after the fact. -
The emoji is the state. A folder may only claim a state its files prove, such as
spec.md,plan.mdorpull-requests.md, and the tool refuses any other move. - The name contains spaces and emoji, so quote it in a shell.
tilda states draws every state and transition. docs/WORKFLOW.md, generated by tilda docs, is the full reference.
The lifecycle
stateDiagram-v2
direction TB
New: ⚪️ New
Researched: 🔎 Researched
Ready: 📋 Ready for Planning
Planned: ⭐️ Planned
Building: 🟡 Building
BuildingUI: 🎨 Building UI
Review: 🟢 Ready for Review
InReview: 👀 In Review
Changes: 🔴 Changes Requested
Approved: ✅ Approved & Merged
New --> Researched: leah
Researched --> Ready: yoda
Ready --> Planned: palpatine
Planned --> Building: luke starts
Building --> BuildingUI: luke done, rey building
Building --> Review: last of the pair
BuildingUI --> Review: last of the pair
Review --> InReview: hansolo starts
InReview --> Changes: rejected
Changes --> Building: luke and rey fix it
InReview --> Approved: a human merges
A plan can also leave the main path. It stops at ⭕️ Technical Block or 🅱️ Product Block when a human has to decide something. It ends at 💩 Scrapped by Review, ☢️ Deferred or ❌ Discarded when it won't be built.
The agents
| Agent | Takes plans at | Moves them to | Writes | Job |
|---|---|---|---|---|
leah-researcher |
⚪️ | 🔎 | spec.md |
Researches the brief in parallel and appends a ## Research chapter |
yoda-writer |
🔎, 🕰️ | 📋 | spec.md |
Writes the full spec (goals, non-goals, scope), or blocked.md when a human must answer |
palpatine-planner |
📋 | ⭐️ | plan.md |
Splits the spec into independent work units, one plan each for back end and front end |
luke-backend |
⭐️, 🟡, 🔴 | 🟢 |
plan-backend.md, pull-requests.md
|
Builds the data, domain, API and tests |
rey-frontend |
⭐️, 🟡, 🎨, 🔴 | 🟢 |
plan-frontend.md, pull-requests.md
|
Builds the interface against Luke's API and proves the two halves work together |
hansolo-reviewer |
🟢, 👀 | 👀 approved, 🔴, 💩 | pull-requests.md |
Reviews the diff against the plan; rejects at most twice; never merges |
lando-broker |
⭕️, 🅱️ | ⭐️ | plan.md |
Folds your answers from blocked.md back into the spec and plan |
r2d2-mechanic |
⭐️, 🟡, 🔴 | 🟢 |
plan.md, pull-requests.md
|
Quick lane only: does one short mechanical task straight from spec.md
|
Luke and Rey work at the same time, in the same worktree, toward one pull request. They talk through tilda mail send and tilda mail read. Rey only starts when plan-frontend.md has work units, so a back-end-only plan costs one builder, not two.
| Agent | Model / effort | Why |
|---|---|---|
leah-researcher |
haiku / medium | lookups, fanned out |
yoda-writer |
sonnet / medium | prose |
palpatine-planner |
opus / high | the one place depth pays |
luke-backend |
opus / high | builds; Opus is the ceiling |
rey-frontend |
sonnet / medium | narrower work |
hansolo-reviewer |
sonnet / high | review |
lando-broker |
haiku / low | clerical |
r2d2-mechanic |
haiku / medium | short mechanical tasks |
No agent runs above Opus: the Claude adapter clamps Fable and anything costlier to Opus. Agents start without your personal plugins, skills, hooks and MCP servers, which on a typical setup cuts a quarter of every turn's context and half the start-up time. tilda run --user-config lets them back in.
Lanes, and what spec.md can ask for
The frontmatter of spec.md steers how the plan is worked. Every key is optional:
---
lane: quick # full (default) | plan | quick
frontend: false # true forces rey-frontend; false keeps it out
depth: fast # fast | medium | deep: the default effort for every phase
phases: # per-phase overrides: research, specification, planning,
build: # build, frontend, review, unblock
adapter: codex # claude (default) | codex | pi
model: gpt-5-codex
effort: medium # low | medium | high | xhigh | max
review: { model: opus }
implementation_suggestions: # text or a list: language, libraries, an approach
- Ruby, with dry-cli for the command line
implementation_requirements: # text or a list: binding, exactly as written
- The database is PG-strict
task-completed-when: # what must be true for the task to be finished
- "`tilda create` opens an editor and writes no draft"
how-to-verify: # the commands or checks that show it
- bundle exec rspec spec/agentilda/cli/create_spec.rb
---implementation_suggestions is advice, not a requirement. Every agent is shown it as "suggestions from the spec's author", prefers it when it fits, and may take a better route as long as it writes why in the document it owns.
implementation_requirements is the opposite: every agent is told to do exactly what each item says. An agent that cannot meet one signs Blocked instead of substituting its own, and hansolo-reviewer rejects work that does not meet them. Agents start without your personal skills (run --user-config restores them), so write the rule itself, not just the name of a skill that holds it.
task-completed-when and how-to-verify are copied into plan.md under ## Task completed when and ## How to verify. Agents are asked to copy them verbatim; after a planner signs, the harness appends whichever section is missing and never touches one that is there. Every agent is told not to sign Completed until the first holds and to run each check in the second, and the reviewer rejects work that fails one.
| Lane | Route | For |
|---|---|---|
full |
leah → yoda → palpatine → luke (+ rey) → han | features you want researched and specified |
plan |
palpatine → luke (+ rey) → han | a spec you already trust; skip research and rewrite |
quick |
r2d2-mechanic → han | "split the big migration into smaller ones" |
tilda create --lane quick split big migration writes the frontmatter for you. Precedence for model and effort, highest first: run --model, then phases.<phase>, then depth, then the agent's own definition.
Run tilda agents list for the same table, or tilda describe <agent> to read one agent's prompt.
Unblocking a plan
The loop never assigns a blocked plan (⭕️ or 🅱️) to an agent. Write your answers into the plan's blocked.md, then:
tilda unblock 003 -n # what is 003 still waiting on?
tilda unblock 003 # fold the answered questions into spec.md and plan.mdPartial answers are fine. The plan stays blocked until the last question is answered.
Commands
tilda init # .tilda.yml and .plans/ at the project root
tilda create tax rule dsl # a new plan: 003.00-⚪️ → tax-rule-dsl
tilda create --lane quick split big migration # one agent, straight to a pull request
tilda create --from notes/dsl.md # a new plan named by the file's frontmatter title
tilda create --after 002 k1 sync # a retroactive plan: 002.01-🕰️ → k1-sync
tilda list-plans # every plan, its state and its pull requests
tilda index # write .plans/INDEX.md
tilda states # the state machine, as a diagram
tilda docs -o docs/WORKFLOW.md # regenerate the conventions from the code
tilda start # the factory as a service: agents plus the control room, until Stop All
tilda run --plan 003 # run the agents on specific plans, until no plan changes state
tilda unblock 003 # fold answered blocks back in
tilda worktree 003 # create and seed a worktree for one plan
tilda resync dirs # rename folders whose emoji their contents contradict
tilda resync prs # add missing [NNN.MM] prefixes to pull request titles
tilda linear import TAX -p 'Tax DSL' # mirror the plans into Linear
tilda skills install # copy the bundled skills into ~/.agents/skillsImportant
Every command does the work it is named for. Add -n / --dry-run to run, unblock, resync dirs, resync prs or linear import to see what it would do first, with nothing changed.
Running the agents
tilda start: the factory with its control room
tilda start # engine plus control room on http://127.0.0.1:4747
tilda start --no-open --port 4848 # print the URL instead of opening it, on another port
tilda start --rescan 10 # look for new or hand-edited plans every 10 s while idletilda start is tilda run that does not stop at a fixed point. When a plan blocks, it idles, puts the questions in Needs You on the page, takes your answer from there, folds it in and hands the plan back to its agent, with no restart. Plans created on the page, by tilda create in another terminal, or edited by hand are picked up on the next rescan (every 5 s, or at once after anything you do on the page).
Every decision goes through the page: answers, hold, wrap up and stop on an agent, model changes, new plans. Stop All stops the agents, then the engine, then the page if start launched it; a tilda web it joined stays up and waits for the next engine. The terminal only prints the URL and the progress log.
It never ends on its own, not even once every plan is finished: the next idea arrives through the page. It needs no plans to start, and makes .plans/ (or the dir: that .tilda.yml names under commands: start:) when it is missing; the page then opens on New idea. Only Stop All, Ctrl-C, SIGTERM or the control room going away end it (a joined tilda web outlives it), each with the same graceful stop as run, and exit 0. It takes run's flags except -n and --no-web, and needs Redis: without it, it refuses (exit 69) rather than going headless.
tilda run: to a fixed point
tilda run -n # dry run: who would take what
tilda run # the whole tree, one worktree per plan, in parallel
tilda run -j 4 # at most four agents at once
tilda run --plan 003,005.01 # only these plans
tilda run --agent yoda-writer --prompt "Rework the risks section first"
tilda run --skip hansolo-reviewer # everyone but the reviewer; its plans wait
tilda run --model opus # one model for every agent
tilda run --user-config # agents load your personal plugins and skills too
tilda run --timeout 600 # at most ten minutes per agent
tilda run --agent luke-backend --prompt steer.md # a short --prompt naming a file is read
tilda run --rounds 1 # at most one round per agent per plan
tilda run --max-tokens 200000 # token budget per agent invocation
tilda run --isolation shared # one checkout, one agent at a time, no git neededIf you just created several plans, run them with a single --plan list. Separate bare run calls each loop over the whole tree, so their worktrees would overlap.
Below is the screenshot of multiple agents running, working on two separate plans. The fist plan has both Luke and Rey (backend and frontend developers) runnign, while the second plan is only at the research phase with Leah.
The loop stops when a full round changes no plan's state. It also stops when every plan in scope is done, blocked, or out of rounds.
Safety rails
-
Isolation. By default every plan gets its own git worktree and branch,
<user>/NNN.MM-slug, so agents on different plans share nothing. Parallel runs require it:--isolation sharedruns one agent at a time. -
Agents cannot publish. They may write source, tests and their plan's documents, but may not commit, push or touch a pull request. The tool enforces this twice: it withholds those commands from the agent, and afterwards checks that
HEADdid not move. -
The harness publishes. When a plan finishes, the harness commits the branch, pushes it and opens a pull request titled like
[002.00](A) Tenancy Households. Pass--no-git-pushto leave the work uncommitted in its worktree. - Nothing merges automatically. An approval leaves the plan at 👀 until you merge.
- Disk over claims. A plan moves only when the files its new state requires exist. If an agent reports success but wrote nothing, the tool records it as interrupted.
Limits
| Limit | Declared by the agent | Flag | Default |
|---|---|---|---|
| Clock |
timeout: in its frontmatter |
--timeout |
900 seconds |
| Rounds |
rounds: (at most 5) |
--rounds |
1 |
| Tokens | none | --max-tokens |
unmetered |
Flags can only tighten what an agent declares, never loosen it. Each agent is told its clock and its token budget in its prompt.
As its time runs out, the agent is warned at 10, 5 and 1 minute left, then asked to stop. If it is still running 60 seconds after that, it is killed. A token budget works the same way: the agent is told the number, and the tool aborts it once it goes over.
Defaults for run live in the project's .tilda.yml, under jobs and commands.run (see Project settings). A typed flag beats the file, and the file beats the built-in default.
Following a run, and stopping it
Every command that runs agents (run, unblock and create) shows each agent as a spinner line on STDERR on a terminal, and writes the same progress to its log everywhere else (a pipe, a dry run, cron). When Redis answers, run also serves a web control room on http://127.0.0.1:4747 and opens it, from which a person can watch the board and message, interrupt, kill or extend an agent (--no-web, --no-open, --port, --redis-url). Without Redis the run goes on headless. tilda web serves the page on its own, and tilda web --demo shows a sample run.
Ctrl-C once asks each running agent to write a RESUME: note into its plan's mailbox.md and sign Interrupted; every agent reads its mail before it starts, so a later run picks up where this one stopped instead of redoing the work. A second press aborts immediately. It works through a SIGINT handler.
How agents hand off
An agent never renames its plan folder. Instead, it signs the plan's state.json, and the harness moves the folder based on the signature:
agentilda state sign --plan 003 --agent leah-researcher --round 1 --status Started
agentilda state sign --plan 003 --agent leah-researcher --round 1 --status Completed --next yoda-writer
agentilda state show --plan 003 | jq '.stages[] | {agent, round, status, model, seconds}'state.json lives in every plan folder and is committed with it. It holds the harness's record of each round (adapter, model, effort, tokens, seconds), the agents' signatures, and the messages between them (tilda mail). Its shape is schemas/plan-state.schema.json. Writes go through a lock, so two agents on one plan cannot overwrite each other. Signatures written the old way, as > [!NOTE] lines in the markdown, are still read.
| Status | What the harness does |
|---|---|
Completed |
moves the folder on, if the files the next state requires are there |
Blocked |
parks the folder at ⭕️, or at 🅱️ when the note says product
|
Almost completed, Interrupted
|
gives the agent another round, up to its limit |
Suppose an agent stops without signing, because it crashed, timed out or was killed. The harness signs Interrupted for it. If the work is on disk anyway, the harness also signs Completed for it. A round left Started by a harness that has since died is found in state.json (and in the run's own gitignored .plans/agentilda-state.json), marked Interrupted, and picked up by the next run.
GitHub and Linear
-
resync dirsrenames folders whose emoji their contents contradict. For example, a ⚪️ with aplan.mdbecomes ⭐️. It is safe to run repeatedly. -
resync prsadds the[NNN.MM]prefix to pull request titles. It works from the branch name first, then from the diff. It reports anything ambiguous and never changes it. It needsgh. -
linear import TEAMworks out one Linear issue per plan and one child issue per work unit, and lists what would be created or updated. It only goes one way: nothing edited in Linear comes back.--format jsonhands the same data to the Linear MCP server, which applies it.--update-unchangedincludes plans that have not changed since the last import.
From Claude Code
The /plan-create, /plan-run, /plan-status and other /plan-* slash commands wrap this same binary. They also add checks, such as previewing the scope with -n before a run. They ship with agentilda-ai-setup. Use them inside a Claude Code session, and the binary everywhere else.
Development
just test # rspec
just lint # rubocop
just format # rubocop -a, then mdformat
just ci # lint, then tests with coverage
just update-workflow # regenerate docs/WORKFLOW.mdThe executables in exe/ find their own Gemfile, so they behave the same whether they are run from PATH or from another project's root.
Factory integration test
just factory-e2e proves the whole factory through the page. It makes a throwaway git repository with no remote, runs tilda init and tilda start --isolation shared there (port 4777, Redis db 11), and Cypress (web/cypress/factory/) then:
- types a tic-tac-toe idea into the New idea dialog, in the quick lane at depth fast, with no front end;
- watches the board, answering any question under Needs you from a canned list;
- waits for
r2d2-mechanicto finish the build. With no remote there is no pull request, so the plan stays at 🟡 Building, done for the run: 🟢 needs an open pull request, and 👀 and ✅ need a person; - serves the
index.htmlthe agent wrote, plays a game X wins, checks the win is announced, and checks Restart clears the board.
It runs real claude agents, so it costs tokens: one Haiku build, about a cent and under a minute of agent time. It refuses to run without AGENTILDA_FACTORY_E2E=1, and nothing in just test, just web-e2e or CircleCI runs it:
AGENTILDA_FACTORY_E2E=1 just factory-e2eFACTORY_PORT, FACTORY_REDIS_URL, FACTORY_MODEL (default haiku), FACTORY_TIMEOUT (seconds per agent, default 600) and FACTORY_MINUTES (the wait for the build, default 15) override the defaults. A port already in use is refused, never taken over. The temp repository and the tilda start it launched are removed on every exit, and what is worth reading afterwards (the board, each plan's state.json, the progress log, the built page and the tail of each agent's trace) is kept in log/factory-e2e/.
© 2026 Konstantin Gredeskoul


