@dfsramos/foreman
v0.17.0
Published
Polling-and-dispatch daemon that hands tracker issues to coding-agent CLIs and follows their pull requests.
Maintainers
Readme
foreman
A polling-and-dispatch daemon that hands tasks to coding-agent CLIs such as Claude Code.
Foreman runs one or more projects. Each project keeps its tasks as Markdown files on a branch of a git repository (see Tasks) and works in one repository. Foreman polls the tasks for ones in Todo, claims each, runs a coding agent in a fresh git worktree, checks that the agent opened a pull request, and follows the pull request through review until it is merged. A web console starts and stops the runner, shows the live agent logs and run history, manages the projects and the agents, and lists, creates and moves tasks. Tasks can also be written by hand, through the MCP endpoint, the console's chat, or imported from Linear or Kaneo (see Importing tasks); after an import, the work happens in foreman and the other tracker is left alone.
It runs as many tasks at once as the enabled agents allow, each in its own worktree. There is no queue beyond the task files themselves and no automatic retry.
How a run works
| Step | What foreman does |
|---|---|
| Claim | Moves the task to In progress, records which machine holds it, and logs that it was picked up. If that fails, the task is skipped and nothing is recorded. |
| Worktree | Runs git fetch, then creates <worktree root>/<task identifier> on a new branch named after the task from the repository's default branch. |
| Setup | Runs the project's setup command in the worktree, such as npm ci, when it has one (see Projects). If it fails, the run fails and the agent is not started. |
| Agent | Runs the chosen agent CLI headless in the worktree, with that agent's own permission settings. The prompt contains the task's title and description, its comments, the worktree path and the repository path. The agent commits, pushes and opens the pull request. |
| Pull request | Looks up the open pull request for the branch with gh pr list --head <branch>. |
| Report | Moves the task to In review, logs the pull request link, and removes the worktree. In the Copilot review mode it also asks Copilot to review (see Review modes). |
After that the task is in review, and foreman keeps following its pull request (see The review loop).
If any step fails, the task stays In progress with an entry in its log explaining why, the worktree is kept, and the run is shown as failed. Foreman never picks that task up again on its own; see Retrying a failed task.
When the agent needs input. An agent that cannot do the task as written, because it needs a decision, information or access it does not have, is told not to guess: it opens no pull request and ends with a question block: a line FOREMAN_BLOCKED: <the question>, then the context that raises it, two to four numbered options with what each would mean, and the option it recommends (for missing information or access, exactly what is needed instead of options). Foreman then logs the whole block on the task, which stays In progress, and shows the run as Needs input. It removes the worktree and deletes the branch, so that a retry starts clean, unless the agent left uncommitted files or commits there; the log entry says what was kept. To answer, comment on the task (its comments go into the next agent's prompt) and move it back to Todo. A run whose agent asked a question is recorded as blocked even when foreman cannot look up its pull request, for example while GitHub is unreachable. A run that opened a pull request counts as finished whatever its last message says.
Aborting a run. Abort run, on a run's header, kills that run's agent and everything it started. The task stays claimed, with an entry in its log. Other runs carry on.
Restarts. If foreman is killed mid-run rather than stopped (see Commands), the agent keeps running on its own until foreman starts again. On the next start the interrupted run is marked failed, the agent and the commands it started are stopped, and the task gets a log entry.
Requirements
- Linux, macOS, or Windows through WSL
- Node.js 24 or later
git, with each project's repository cloned locally and anoriginremote- GitHub CLI (
gh), authenticated for those repositories - At least one supported agent CLI, installed and signed in (see Agents)
- Push access to the repository that holds a project's tasks, unless they are kept on this machine only (see Tasks). An API key only to import from Linear or Kaneo (see Importing tasks)
Foreman finds the default branch through origin/HEAD. If a run fails with "Could not determine the default branch", run git remote set-head origin --auto in the repository.
Install
npm install -g @dfsramos/foreman
foreman startforeman start runs foreman in the background and prints the console's address, http://127.0.0.1:4310 by default. Open it, add a project on the Projects tab, and press Start.
fm is the same command as foreman, for anyone whose foreman is already the Ruby Procfile runner of that name.
Commands
| Command | What it does |
|---|---|
| foreman start | Starts foreman in the background, writing its output to foreman.log in the data directory. Does nothing if it is already running. |
| foreman stop | Stops polling, waits for the runs in progress to finish, then exits. The console stays reachable while it waits. |
| foreman stop --now | Aborts the runs in progress instead of waiting. Their tasks stay In progress, as after any failed run. |
| foreman restart [--now] | Stops foreman as foreman stop does, then starts it again. The projects that were running start again too, not only those set to Start with foreman. When foreman is not running, it just starts it. |
| foreman update [--now] | Installs the newest version from npm when it is newer than this one, stopping foreman first and starting it again on the new version, with the projects that were running. Only for foreman installed with npm install -g; otherwise it says how it found foreman and changes nothing. A failed install leaves the old version in place and running. |
| foreman status | Shows the console's address, the data directory, each project's state and the runs in progress. Exits with 3 when foreman is not running. |
| foreman logs [-n N] [-f] | Prints the last N lines of the log (default 100); -f keeps following it. |
| foreman run | Runs foreman in the foreground, printing to the terminal; for a service manager or debugging. Ctrl-C aborts the runs in progress. |
| foreman version, foreman help | Print the version, or the list of commands. |
Sending SIGTERM to foreman waits for runs in progress as foreman stop does; SIGINT, or a second signal while waiting, aborts them as foreman stop --now does. Under a service manager, allow for that wait: systemd, for one, kills a service 90 seconds after asking it to stop unless TimeoutStopSec says otherwise.
Foreman finds the agent CLIs, git and gh on the PATH it was started with, and passes its environment on to them. foreman start from your shell inherits your shell's; a service manager usually starts with a shorter PATH, which then needs to include where those CLIs are installed.
Settings
The process itself needs only three settings, all optional, read from the environment or from .env in the data directory:
| Variable | Default | Purpose |
|---|---|---|
| DATA_DIR | ~/.local/share/foreman on Linux and WSL (or $XDG_DATA_HOME/foreman), ~/Library/Application Support/foreman on macOS | Where foreman keeps everything (below). Set it in the environment only, since .env is found through it. |
| HOST | 127.0.0.1 | Address the console listens on. |
| PORT | 4310 | Port the console listens on. |
A value already set in the environment wins over .env. Everything else is stored in the database and edited in the console.
The data directory holds:
| Path | Contents |
|---|---|
| foreman.db | The SQLite database: projects, agents, run history and import connection keys. |
| .env | Optional settings, as above. |
| foreman.log | Output of foreman start. Moved to foreman.log.1 on start once it is over 10 MB. |
| foreman.pid | The process ID while foreman is running. |
| resume.json | Written by foreman restart and foreman update before they stop foreman: the projects to start again. Read and removed by the next start. |
| tasks/ | Clones of the branches that hold Git branch tasks. |
| worktrees/ | The agents' worktrees, unless a project sets its own worktree root. |
Upgrading from a checkout
Before 0.2.0, foreman ran from a copy of its source code, kept its data in ./data in the directory it was started from, and read .env from that directory. To keep using that data, stop foreman and either set DATA_DIR to the old directory, or move it, before the new location exists (an installed foreman that has already started creates it; remove it first if it holds nothing you need):
mv ./data ~/.local/share/foreman # Linux and WSL
mv ./data ~/Library/Application\ Support/foreman # macOSThen move the old .env, if there is one, into the data directory, and drop any DATA_DIR line from it.
After moving the data, two things still point at the old place, because both are stored as absolute paths:
- Worktree roots. A project created with the default worktree root has the old
data/worktrees/<project>path saved. Edit each such project's worktree root in the console to the new location. - Open worktrees. Git records where each worktree is. In each repository that still has a foreman worktree, run
git worktree repair <new worktree path>, or move the data only once those worktrees are gone.
Upgrading from a single-project setup. The variables of the setup from before 0.2 (LINEAR_API_KEY, LINEAR_TEAM_ID, LINEAR_PROJECT_ID, REPO_PATH and the rest) are no longer read; add the project on the Projects tab. AGENT_ALLOWED_TOOLS and CLAUDE_BIN are only read as initial values for the claude agent.
Projects
A project connects a task branch to one repository. On the Projects tab each project has:
| Setting | Meaning |
|---|---|
| Name | Shown on runs and in the history. Unique. |
| Priority | Low, Normal or High: how often the project gets an agent when projects compete (see below). |
| Source | Where the task files are: storage, repository, branch, directory and identifier prefix. See Tasks. |
| Repository path | A local git repository. A repository can belong to only one project. |
| Worktree root | Where this project's worktrees go. Defaults to <DATA_DIR>/worktrees/<project>. |
| Setup command | Run with sh in each worktree before the agent starts, for review rounds too, such as npm ci, so the agent finds the dependencies installed. It gets the agent's environment and 15 minutes; if it fails or runs out of time, the run fails with the end of its output, and the agent is not started. It must be safe to run again, since a review round can reuse a worktree. Empty runs nothing. |
| Poll interval | Seconds between polls, 10 to 86400. |
| Agent timeout | Longest an agent may run on one task, 1 to 1440 minutes. |
| Stall timeout | How long an agent may print nothing before foreman stops it and fails the run, up to 1440 minutes, so a hung agent does not hold its slot until the agent timeout. Any output counts, but an agent prints nothing while a command it started is running, so set it above your slowest build or test suite. Empty or 0 turns it off. |
| Allowed agents | Which agents may work on this project. None selected means any. |
| Agent instructions | Text added to every prompt for this project, work and review rounds alike, under "Project instructions" and before foreman's own instructions, which win where the two disagree. For rules an agent would not find in the repository, such as which package manager or test command to use. Up to 10,000 characters; empty adds nothing. |
| Start with foreman | Begin polling when foreman starts. A new project is created stopped. |
| Rounds of feedback per task | How many rounds of follow-up a task gets (review feedback, merge conflicts and agent reviews all count) before a person takes over. 0 turns the review loop off. Default 5. |
| Ignore comments from | GitHub accounts whose pull request comments are not review feedback, such as deployment bots. |
| Review | Who is asked to review a pull request once it opens: No review (the default), Copilot review, Another agent, or Any agent (prefers another). See Review modes. |
| Wait for green CI | On by default. Failing checks go back to the agent, and approvals wait until the checks pass. See CI. |
| Notification URL | Where foreman posts what starts waiting on you. See Notifications. |
Check source tries the source settings without saving. For a remote branch it creates the branch when it is missing and checks that pushes work; either way it reports how many tasks each state holds and which machine polls the project. Creating or saving a project runs the same check and shows its result above the project list; the project is saved whatever the check finds, and the message says what to fix. A problem with one project's source is shown against that project and does not affect the others.
Starting and stopping. Each project is started and stopped on its own, from the Run tab or the Projects tab; the header has Start all and Stop all. Stopping a project ends its polling and lets its runs in progress finish. A project can be deleted once it is stopped and has no runs in progress; its past runs stay in the history, and its task files and the repository are left as they are.
Sharing agents between projects. All projects draw on the same agent slots. A slot that frees up goes at once to a project with work waiting. When several are waiting they take weighted turns: for every one turn of a Low project, a Normal project gets two and a High project three. No project is starved, however busy the others are. Within a project, tasks are taken by priority, then by number.
Linear and Kaneo as project sources were removed in 0.13. On its first start after the upgrade, foreman deletes the projects that still used them, keeping their runs in History. To carry on with one, add a project for the same repository and import its open issues.
Tasks
A project keeps its tasks as one Markdown file each, in a directory on a branch of a git repository: by default an orphan branch foreman of the project's own repository, under .foreman/tasks. There is no tracking service to run. Foreman reads and writes the files through a clone under <DATA_DIR>/tasks/ and pushes every change at once, so any machine with push access sees the same tasks and can take a project over, mid review loop included. A project can instead keep its tasks local only: the same files, in the same clone, with nothing fetched or pushed.
| Setting | Meaning |
|---|---|
| Storage | Remote branch (default) syncs with the branch on the remote, as described here. Local only keeps the tasks in the clone under <DATA_DIR>/tasks/ on this machine alone: nothing is fetched or pushed, there is no lease, no credentials are needed, and Repository is never contacted. |
| Repository | URL or path of the repository that holds the task files. Blank means the project's own origin remote. Point it at a separate private repository to keep foreman's data out of the project; several projects can share one, with a directory each. |
| Branch | foreman by default. Check source creates it on the remote when it does not exist. Mind the spelling: a misspelt name becomes a new branch. |
| Directory | .foreman/tasks by default, relative to the repository root. |
| Identifier prefix | FM by default: the task in 12-add-login-page.md is FM-12. |
Creating the branch. Foreman creates it, as one empty commit with no parent, the first time the project is saved or checked; the task files stay out of the project's history. A second worktree on the branch is the place to write tasks from the command line, once it exists:
git fetch origin foreman
git worktree add ../myproject-tasks foremanA branch created by mistake, under a misspelt name say, is that one empty commit; remove it with git push origin --delete <name>. Check source reports that pushes work, how many tasks the directory holds in each state, and which machine polls the project.
The file format. A task is <number>-<slug>.md: an optional frontmatter block of flat key: value lines between two --- lines, a # title, the description, and a ## Log that foreman appends to. This one is being worked on:
---
state: in_progress
priority: 2
created: 2026-10-23T10:00:00Z
claimed_by: laptop
claimed_at: 2026-10-23T10:05:00Z
branch: fm-12-add-login-page
---
# Add login page
Users sign in with their email address and a password. Keep the form on `/login`.
## Log
### 2026-10-23T10:02:00Z · laptop
Use the shared button component for the submit button.
### 2026-10-23T10:05:00Z
Foreman: picked up. Claude Code (instance claude-7ojxci) is working on it.A log entry whose heading has only a time is foreman's. One with a name after the time is a person's comment, signed with the hostname of the machine foreman runs on. One with (agent) after the name, such as ### 2026-10-23T10:20:00Z · Claude Code (agent), is the agent's final message of a run, which foreman writes just before its own entry for that run.
stateistodo,in_progress,in_review,pending_approvalordone; a file with no frontmatter, or withoutstate, is a todo.priorityis 0 to 4: 0 is none, 1 urgent, 4 low. Todo tasks are taken by priority (none last), then by number.- You write
state,priority, the title and the description, and may add keys of your own; foreman keeps keys it does not know. - Foreman writes
claimed_byandclaimed_at(the machine that holds the task and since when),branch(the agent's branch, fixed when the task is claimed),pr(the pull request's link, written when the task moves to in review or to pending approval),in_review_sinceandreview(its review progress, as one JSON line), movesstate, and appends to the log. The Import tab and the Tasks tab writecreated. Each change is one commit on the branch, asforeman <foreman@<hostname>>, with a message such asClaim FM-12 on laptop.orMove FM-12 to in review.; a push that is rejected because another machine pushed first is re-read and retried.
Writing a task. Create <number>-<slug>.md in the directory with the next free number, commit and push; a file holding only # Add login page is a valid todo. The number is the identifier (FM-12) and must not be reused; a done task's file stays where it is. Two files with one number stop the poll with a message against the project in the console, until one of them is renamed. The Tasks tab does the same from the console, and the Import tab copies tasks in from Linear or Kaneo.
Retrying. A failed or blocked task stays in_progress with its claim. To retry it, move it to Todo on the Tasks tab or with the move_task MCP tool, which also removes the worktree and branch the run left on this machine (see Moving a task below). Setting state: todo and removing claimed_by and claimed_at by hand works too, but leaves the worktree and branch for you to remove, as under Retrying a failed task.
One machine at a time. A project is polled, dispatched and reviewed by one machine at a time, the one that holds its lease: the file .lease in the task directory, one JSON line naming the holder and when the lease runs out, ten minutes after it was taken or last renewed. The holder renews it while it polls. Other machines read the tasks but start no agent and act on no review. When a project is stopped, its machine hands the lease back once the project's runs are over, and the next machine that polls takes it over at once; a machine that is cut off leaves a lease that lapses within ten minutes, and the next poll after that takes it over. Either takeover clears the claims the previous machine left, with a log entry on each task (Clear claims left by <host>.); those tasks keep their state, where a failed run leaves them, and are retried as above. A run that outlives the lease still finishes under its own claim; one whose machine was cut off for longer than that finds its claim cleared when it finishes, and fails there. The Tasks tab and Check source show who holds the lease.
Credentials. Foreman itself pushes, so the account the daemon runs under needs push access to the repository without a prompt: for an HTTPS remote, gh auth setup-git or another credential helper; for SSH, a key without a passphrase or an agent that has it loaded. Check source runs a dry-run push and reports git's message when that fails, with the fix for an HTTPS or SSH remote when credentials are the cause; a machine that starts the project without them shows the same message against the project on its first poll. This source stores no key of its own.
Local only. With Storage set to Local only, foreman keeps the task files in the clone under <DATA_DIR>/tasks/ and touches no remote: each change is a local commit, nothing is fetched or pushed, and there is no lease because one machine owns the tasks. The Tasks tab, imports and the MCP endpoint work as before; to write a task by hand, edit the clone that Check source names and commit there. A project switched to Local only keeps the tasks its clone already holds, and the branch on the remote is left as it was (git push origin --delete foreman removes it). Switched back to Remote branch, foreman pushes the local commits when the remote branch is missing or behind them, and reports a history that went both ways for you to push or reset by hand before it syncs again. Repository still names which clone is used, so leave it as it was when switching; a local-only project with a blank Repository and no origin remote keeps its clone under a name made from its own path.
Using main instead. Branch can be set to main, or any other existing branch, and the task files then live in that branch's tree; but every claim, comment, lease renewal and state move is then a commit on main, and every workflow with on: push and no branch filter runs on each of them. The orphan branch carries no workflow files, so GitHub Actions does not run on it; a CI system that triggers on every branch of the repository does, and needs a branch filter.
Tasks tab. The console's Tasks tab lists a Git branch project's tasks grouped by state, shows which machine holds the project's lease, and has a form that creates a task file on the branch (one commit, Add FM-13: <title>.) and an Edit button on each row that changes a task's title, description and priority (one commit, Edit FM-13: <title>.; the file keeps its name, state, claim and log), and a Move to list on each row that moves a task to another state (one commit, Move FM-13 to todo.). It is the way to add or change a task from a machine that has no worktree on the task branch.
Moving a task. A move clears the task's claim and adds a log entry such as Moved from in progress to todo on laptop. Cleared the claim of desk. A move to Todo is how a failed or blocked task is retried. On this machine it first removes the worktree and local branch the task's last run left, so the next run starts clean; when they hold uncommitted files or commits no remote has, the move is refused and says what is there, and Discard and retry removes them anyway after asking (discard: true for move_task). A move to In review records in_review_since, so only pull request feedback newer than the move starts a round. A task that a run on this machine is working on cannot be moved until the run ends or is aborted. A claim held by another machine is cleared after the console asks for confirmation; if a run there is still working on the task, it fails when it finishes, as after a lease takeover.
Comments. To ask an agent for a change or more work, open a task's log with the Log button on its row and write a comment, or use the comment_task MCP tool. Each comment is one commit (Comment on FM-12 by <hostname>.). What happens next depends on the task's state:
todo: the comment goes into the prompt of the agent that picks the task up.in_revieworpending_approval: the next review check hands it to an agent as a new round, with any new pull request feedback. The round counts toward the limit, but a comment resets the count to 1 first, so it is never handed over to a person.in_progress: the comment waits, and the next review check after the run ends hands it out.done: the comment is only recorded.
The review line records the newest comment an agent was given as commentsHandledUntil; later ones are new. A comment on a task in review whose pull request cannot be found waits until it exists.
MCP endpoint. While foreman runs it serves MCP at http://127.0.0.1:4310/mcp (streamable HTTP, answered as JSON, no sessions), so an AI client can read and write tasks. Add it to Claude Code with claude mcp add --transport http foreman http://127.0.0.1:4310/mcp. The tools are get_status, list_projects, list_runs, get_run, list_attention, list_tasks, get_task, create_task, update_task, move_task and comment_task; the last six read and change tasks, and the last four make the same commits as the Tasks tab, under the same rules. list_projects gives each project's id, name, source type, repository path (repo_path) and priority. list_runs and get_run are read-only: list_runs lists runs newest first, with how each ended and why it failed, and can filter by project and status; get_run returns one run with the end of its log (200 entries unless asked for more), so a client can explain a failure. get_task includes the task's log, which list_tasks leaves out. list_attention lists what waits on a person on this machine, one item per task: a run that ended blocked, failed or aborted on a task this machine still claims; a task in progress that nobody claims; a pull request handed to a person; and one waiting to be merged. Each item gives the reason, the agent's last message (from the newest run that has one), the kept worktree with its uncommitted files and unpushed commits, and what to do next. It can be filtered by project and by kind. There is no tool that starts or stops a project, aborts a run or changes a setting. The endpoint has no authentication, so it is served only while HOST is a loopback address, and it gets the console's hostname and cross-origin checks.
Importing tasks
The Import tab copies tasks from Linear, Kaneo, GitHub Issues or Jira into a project, so any of them can stay a place to browse and plan while foreman works from the copy. Foreman only reads from them: it never adds a label, a comment or a state change there, and keeping them up to date afterwards is up to you.
- Add an import connection. For Linear, a personal API key. For Kaneo, its address (for example
http://localhost:5173; the API is under/api) and an API key (Kaneo's Settings, API keys). For GitHub, nothing: it reads through the GitHub CLI as the accountghis signed in to, the one foreman already uses for pull requests, so no key is stored; set Host only for a GitHub Enterprise server. For Jira, the site address (Jira Cloud, such ashttps://acme.atlassian.net), the email of the Atlassian account and an API token from that account (id.atlassian.com, Security, API tokens). Check tries it without saving. - Choose the connection, the project to copy into, the team (a Kaneo workspace, a GitHub repository, a Jira project), optionally a project there, and the states (columns in Kaneo, Open and Closed on GitHub; Backlog, Todo, To Do and Open are ticked by default), and press Show tasks.
- Tick tasks and import them. Each becomes a new Todo task file on the project's task branch (one commit each,
Add FM-13: <title>.), so foreman picks it up on its next poll.
An import is a one-time copy of the title, the description and the priority. The description ends with a line Imported from <identifier> · <url>, which is also how foreman recognises a task it has already copied: those are shown as imported and skipped. Edits made in the other tracker afterwards are not carried over. Kaneo's planned and archived tasks are not listed, and Kaneo tasks show no assignee. On GitHub, pull requests are left out, the repositories offered are those the account owns, collaborates on or reaches through an organisation, and issues have no priority. Jira descriptions are converted to Markdown, leaving out attachments and images, and Jira's Highest to Lowest priorities become Urgent to Low. Jira Server and Data Center are not supported.
The review loop
Opening the pull request is not the end. A task moves through these states, and foreman acts in each:
| State | How a task gets there | What foreman does | |---|---|---| | Todo | You put it there, or an import does. | Picks it up. | | In progress | Foreman, when an agent starts on it. | The agent works; a log entry says which agent. | | In review | Foreman, once the pull request is open. | Watches the pull request for new feedback and for merge conflicts. | | Pending approval | Foreman, once the review is positive. | Waits for a person to approve and merge; watches for the merge, for further feedback and for merge conflicts. | | Done | Foreman, once the pull request is merged. | Nothing more. |
Feedback. New reviews, comments and line comments on the pull request are handed to an agent together. On a Git branch project, a person's comments on the task are feedback too, handed to the agent under "New comments on the task". The task goes back to in-progress while the agent works. The agent decides what the feedback amounts to and ends with a verdict:
- it made changes and pushed them: the task returns to review, for another look;
- the feedback approves the pull request or recommends approval: the task moves to Pending approval;
- nothing needed changing and it is not an approval: the task returns to review.
Whenever a task returns to review, foreman asks the authors of that round's feedback to review again (gh pr edit --add-reviewer, with Copilot requested as @copilot). The pull request's own author is never asked, and an account GitHub will not accept as a reviewer is noted in the run log and skipped.
Merge conflicts. On each check foreman also asks GitHub whether the pull request can still be merged. One that conflicts with its base branch is handed to an agent, which merges the base into the branch, resolves the conflicts and pushes, without rebasing or force-pushing. This counts as a round, and happens for tasks in review and for tasks awaiting approval; the task goes back to where it was afterwards. A conflict and new feedback found together are handled in one round. If the conflict is still there when the round limit is reached, foreman hands the task to a person and does not raise that conflict again.
A review that is a bare GitHub approval, with no text and nothing else alongside, moves the task to Pending approval without using an agent.
Limits and safeguards.
- After the configured number of rounds on one task, foreman stops, logs that a person is needed, and moves the task to Pending approval.
- Feedback is handled once. Everything on the pull request when a round ends, including the agent's own replies, counts as dealt with. Feedback posted while an agent is mid-round is therefore not picked up separately; the agent may or may not have seen it.
- Comments from the accounts in "Ignore comments from" never start a round. The default list covers common deployment and tracker bots; review bots are not on it, so their reviews are acted on.
- Pull requests are checked at most every two minutes per project.
- A pull request closed without merging is left alone, as is a task with no pull request for its branch.
- A failed review run leaves the task in progress with a comment, like any failed run.
- With "Wait for green CI" on: at most 2 reruns of failed checks per pull request head, and checks pending for 2 hours hand the task to a person.
CI
With Wait for green CI on (the default), the pull request's checks take part in the loop. Foreman reads the checks branch protection marks as required; when the branch has none, which is always the case for a private repository on a free GitHub plan, every check counts. A pull request with no checks counts as green.
- Failing checks become a round for the agent that wrote the pull request. Its prompt lists each failing check with its link and, for GitHub Actions, an excerpt of the failed job's log up to its last error. The agent fixes and pushes, or says the failure is not caused by the change. This counts as a round toward "Rounds of feedback per task".
- Approvals wait for green. A bare GitHub approval, a round whose verdict is approval, and an agent reviewer's approval move the task to Pending approval only when the checks pass or there are none. Otherwise the approval is kept for that head and applied once the checks pass.
- Agent reviews run only on a head whose checks pass or that has none.
- Pending checks wait. Checks still pending 2 hours after foreman first saw them hand the task to a person.
Failures the agent cannot fix. Not every red check is the code's fault:
- A GitHub Actions job that was cancelled, timed out or failed to start is rerun (
gh run rerun <id> --failed) without an agent and without using a round. - In a CI round, the agent ends with
FOREMAN_CI: fixed, or withFOREMAN_CI: not_code <reason>when the failure is not caused by the change: a missing secret, an outage, a quota, a test that also fails on the base branch. Afternot_code, foreman reruns the failed jobs once. - One head gets at most 2 reruns, counting both kinds. Checks that still fail after the rerun the agent asked for,
not_codeon checks foreman cannot rerun (only GitHub Actions jobs can be), and an agent that says it fixed the checks but pushed nothing all hand the task to a person, with the agent's reason and the failing checks in the comment. - After a hand-over, that head's failing checks are not raised again. A new push starts the count afresh.
Review modes
Each project's Review setting decides who is asked to review a pull request once it opens. Feedback from people and bots is handled as described above in every mode.
| Mode | When the pull request opens | Afterwards | |---|---|---| | No review (default) | Nothing. A person requests or does the review. | Whatever review arrives goes through the loop above. | | Copilot review | Foreman requests a review from GitHub Copilot. If GitHub refuses, for example because Copilot review is not available for the repository, the comment on the task says so. | Copilot's review is ordinary feedback, and Copilot is asked again after each round. | | Another agent | An enabled agent other than the one that wrote the pull request reviews it. With no other agent enabled, nothing happens. | See below. | | Any agent (prefers another) | As Another agent, but when no other agent can take it, the author's own agent reviews it in a fresh session, as a self-review. | See below. |
Agent reviews. The reviewing agent reads the diff, can run the project's tests, changes nothing, and posts one comment on the pull request (gh pr review --comment). It ends with a verdict:
- approve: the task moves to Pending approval, and a person approves and merges;
- changes: its findings are handed to the author's agent as the next round of feedback; once that agent pushes, the new head is reviewed again.
An agent review counts as a round toward "Rounds of feedback per task", so two agents that keep disagreeing end with the task handed to a person. The review loop must be on (rounds above 0) for agent reviews to happen. Each head is reviewed once: a review that fails, or ends without a verdict, is not repeated until something new is pushed. While an agent reviews, the task stays in review.
Both agents post as the same GitHub account, and GitHub does not let an account approve its own pull request, so an agent's approval lives in foreman (the task's state), not in GitHub's review status. Branch protection that requires an approving review still needs a person.
Agents
Foreman drives agent CLIs through adapters. At startup, and whenever you press Check again on the Agents tab, it looks for each CLI on the PATH and shows whether it is installed and which version.
Sign-in check. Before an agent is given a task or a review round, foreman asks its CLI whether it is signed in. A signed-out agent takes no work: the task is left where it is, untouched, and the Agents tab and the project show "Not signed in". Foreman asks again a minute later, and the agent resumes by itself once signed in. Only Claude Code has this check (claude auth status); for the other agents, and whenever the answer cannot be read, foreman cannot tell, and a sign-in problem appears as a failed run.
| Agent | CLI | Status |
|---|---|---|
| Claude Code | claude | Verified end to end. Enabled by default. |
| Codex CLI | codex | Written from its documentation; not yet run against the real CLI. |
| opencode | opencode | Written from its documentation; not yet run against the real CLI. |
| Antigravity CLI | agy | Checked against the output of version 1.5.0. Needs 1.2.10 or later. |
Unverified adapters are labelled as such in the console and are disabled until you enable them.
Each agent has, on the Agents tab:
- Enabled and Run at once: how many tasks it may work on at the same time (0 pauses it).
- Its own permission settings, in that CLI's terms: an allow-list for Claude Code, a sandbox mode for Codex, a permission config for opencode, and so on.
- Binary: a command name or absolute path, if it is not the default.
Model and advisor (Claude Code). Two optional settings choose the models a Claude Code run uses. Left empty, the command is the same as without them and the run uses the account's default model.
- Model is passed as
--model: an alias such assonnet,opusorfable, or a full name such asclaude-sonnet-5-5. The run log includes the model the CLI reports, such asModel: claude-sonnet-5-5. - Advisor model is passed as
--advisor. A stronger model reads the run's transcript and is consulted at decision points, such as before settling on an approach and before declaring the task done; each consultation shows in the log as anadvisortool call. Claude Code's documentation says pairing a faster model with a stronger advisor typically costs less than running the stronger model throughout, without giving figures; each run's cost, which includes the advisor's share, and its outcome let you check this on your own tasks. Things to know, from the advisor documentation:- It is experimental: its behaviour, pricing and availability may change. The flag is not listed in
claude --help; it was checked with-pon CLI 2.1.319. - It works only on the Anthropic API and subscription accounts, not on Amazon Bedrock, Google Cloud or Microsoft Foundry.
- On a subscription, advisor usage counts against the plan's usage limits.
- The advisor must rank at or above the model (for example, Opus 5.5 may advise Sonnet 5.5, but not the other way round). A pairing that is not allowed is not attached, and the run goes ahead without an advisor and without an error.
- It is experimental: its behaviour, pricing and availability may change. The flag is not listed in
Plan usage. Claude Code reports how much of the account's subscription limits has been used, per limit window (5-hour and 7-day). Foreman shows the latest figures in the header and on the Agents tab, and uses them in two ways:
- Pause above (% of plan): the agent takes no new tasks once any window reaches this percentage (default 90). It resumes by itself when that window resets. Runs already in progress are not interrupted.
- When spending paid overage: pause (the default) or keep working.
Each run also shows an estimate of how much of each window it used. The figures are for the whole account, so anything else using the same subscription at the same time is included, and a run that overlapped another is marked as shared. The dollar cost shown for Claude Code is the list-price equivalent, not a charge on a subscription. This comes from an undocumented event in the CLI's output, so it may stop working with a future CLI version; the other agents do not report plan usage.
Instances. Each run of an agent gets an instance id of its own: the agent's id and six random characters, such as agy-k3f9q2. The agent is told to end its pull request description, its reviews and its comments on the pull request with a signature naming itself and that id, such as — Antigravity CLI · instance agy-k3f9q2, so each post on GitHub can be traced to the run that wrote it. The id is shown on the Run tab while the agent works, in History, and in foreman's "picked up" entry on the task. Runs recorded before instance ids existed are given one when foreman starts. The signature is an instruction in the prompt, so an agent can still leave it out.
Routing. On each poll, a task goes to the first enabled, installed agent with a free slot, in the order shown on the Agents tab; use the arrows to change the order. When every slot is taken, the remaining tasks wait in Todo for a later poll. The agent that ran a task is recorded on the run.
Record. Each agent's card shows its record over every finished run: how many tasks it took and how many of those opened a pull request, failed, needed input or were aborted; how many review rounds it handled; the median time per task; and the list-price cost, where the CLI reports one. Use it to decide the order above. The counts cover all projects together and all time.
The non-Claude adapters default to the settings under which each CLI can commit, push and open a pull request, which for all three means running without a sandbox or with every permission granted. Read the notes on each agent's card before enabling it.
The office
The Office tab shows the same state as the rest of the console, drawn as a pixel-art office seen at an angle, isometric style: a work room with the boards and desks, and a lounge through a door in the divider. It changes nothing; it is a different view of what foreman is doing.
- Boards. Each project has a cork board on the work room wall with a note per task. The note's colour says where the task stands: waiting, in review, awaiting approval, or stalled (its last run here failed, needed input or was aborted). Stalled notes are pinned first. A board fills once its project has polled since foreman started, and a task being worked on is at a desk, not on the board.
- Desks. There is a desk for each agent slot. Each run in progress has someone at a desk with the task's identifier on it: a page with red pen on the desk for a review round, a magnifying glass for an agent review. The screen's colour follows the run's step.
- The lounge. Agents with a free slot wait in the lounge. An agent paused for its plan usage naps.
- Monitors. While an agent works, its monitor flashes an icon for what its log last showed: editing files, running a command or tests, reading the code, looking something up, thinking, or another tool.
- The foreman stands by the boards, with a "!" when a project's last poll failed. The call button flashes while work is waiting for a free agent. When foreman is stopped, the lights are out and the foreman reads the paper.
- The money ticker in the top right corner shows what every finished run has cost at list price, as the agents report it, and counts up as runs finish. Agents that report no cost add nothing to it.
- The tube. A task that appears in Todo after the first poll arrives through the pneumatic tube; one whose pull request was merged leaves by it.
Changes play out as short scenes:
- A run starts. The foreman presses the button, and one of the agent's people in the lounge jumps up, fetches the note from the foreman and sits at a desk.
- A run ends. The person hands the note back and returns to the lounge, and the foreman pins it on the board in its new colour. A blocked run gets a "?" and a nervous sweat first; an aborted one is unplugged, its plug sparking.
- A run fails. The person passes out at the desk until the foreman comes over, shouts "Wake up!" and takes the note back to the board. If the agent's next run starts first, they wake up for it at once.
- A task arrives. The foreman fetches it from the tube and pins it up. A merged task is taken down and sent up the tube in a shower of confetti.
While a note is on its way it is off the board; it is shown again after 45 seconds whatever happens. The scenes are only for show: a tab that was hidden, or a change in the number of desks or boards, puts everyone straight where they belong.
Hover over a note, a person or the foreman for details. Clicking someone at a desk opens their run on the Run tab; clicking a stalled note opens its last run in History, and any other note opens the task.
Stalled notes are kept in memory: one stays on the board until its task file shows it as waiting, in review or awaiting approval again, or until foreman restarts. A task a person moves straight to done therefore stays stalled until then.
Chat
The button at the bottom right of every tab opens a chat with Claude Code about foreman: ask why a run failed, what is waiting, or to add a task.
- What it can do. Each message runs Claude Code headless (
claude -p) with no built-in tools and only foreman's MCP tools. It can read foreman's status, projects, runs with their logs and tasks, and create, edit, move or comment on tasks of Git branch projects. It cannot run commands, edit files, start or stop projects or abort runs. A task change is a commit on the task branch, like one made on the Tasks tab. - Which Claude Code. It uses the Claude Code agent's binary, model and advisor from the Agents tab. Its usage counts against the same plan windows as the agents' runs.
- One conversation. The conversation is kept across reloads and restarts until you press New chat. One reply runs at a time; Stop ends it.
- Where you are. Each message tells Claude Code which tab you are on, and which run (History) or project (Tasks) you are looking at, so "why did this fail?" means the run on screen.
- When it is available. Only when
HOSTis a loopback address, because it reaches foreman through the MCP endpoint, and when Claude Code is installed and signed in. Otherwise the panel says why.
Replies are shown as plain text. Claude Code keeps the session under ~/.claude/projects/, for the working directory <DATA_DIR>/chat.
Notifications
A project with a Notification URL has each new item that waits on a person on this machine posted there once, as JSON. The items are the ones list_attention lists (see Tasks): a run that needed input, failed or was aborted on a task this machine still claims, a task in progress that nobody is working on, and a pull request handed over to a person or ready to merge. Foreman checks a few seconds after the project's board changes, that is after a poll answers or a run starts or ends, so a stopped project sends nothing new.
{
"text": "[widgets] FM-12 Add login page needs an answer: Which session store should this use?",
"content": "[widgets] FM-12 Add login page needs an answer: Which session store should this use?",
"event": "blocked",
"project": { "id": 1, "name": "widgets" },
"task": { "identifier": "FM-12", "title": "Add login page", "url": "…", "state": "in_progress", "pr": null },
"reason": "Which session store should this use?",
"run": 52,
"console": "http://127.0.0.1:4310/#history/52"
}text is the line Slack's incoming webhooks show and content the one Discord's show; the rest is for anything else that reads JSON, such as a script or an automation service. Posting to Slack and Discord has not been tried against the real services. event is one of blocked, failed, aborted, unclaimed, handed_over and awaiting_approval.
- Each item is sent once. An item that is dealt with and comes back, or comes back with a new run, is sent again.
- What already waits when the URL is first set is not sent.
- A post that fails is tried again on the next check, and the failure is written to foreman's log.
- Send test on the project form posts a sample to the URL typed there, saved or not, and shows how it answered.
Retrying a failed task
Read the failure reason on the run in the console, or in the task's log.
Move the task to Todo on the Tasks tab. Foreman removes the worktree and branch the run left on this machine, and picks the task up on the next poll. If they hold uncommitted files or commits no remote has, the move says what and offers Discard and retry. To keep that work, commit and push it from the worktree first; the move then goes through. To remove them yourself instead:
git -C <repository> worktree remove --force <worktree root>/<identifier> git -C <repository> branch -D <branch>The move leaves anything the run pushed to
originalone, along with any pull request on it. When the next run finds the task's branch already onorigin, it works on the first free<branch>-2,<branch>-3and so on instead, and records that as the task'sbranch. Delete the old remote branch when you no longer need it.
Foreman refuses to start a run if the worktree directory or the branch already exists, so that a previous attempt is never overwritten. The exception is a review round: a worktree left by an earlier round that failed is reused when it is clean, on the pull request branch and not diverged from what was pushed. One that is not is left for you to look at, and the round fails until it is removed.
Security
- No authentication. The console can start and stop the runner and change what the agent is allowed to run. It listens on
127.0.0.1by default, and rejects requests addressed to other hostnames and cross-origin writes. SettingHOSTto a non-loopback address removes the hostname check and exposes the console to that network; put it behind something that authenticates if you do. - The chat starts Claude Code on request. Like the rest of the console, the chat route has no authentication and relies on the loopback hostname check and the cross-origin check. When
HOSTis not a loopback address it refuses every message, since the MCP endpoint it needs is not served. The CLI it starts gets no built-in tools, only foreman's MCP tools, so it cannot run commands or edit files; it can change tasks, as the MCP endpoint can. Your own Claude Code settings still apply to it, hooks included. - Each agent's permission settings are its boundary. Task text is untrusted input to the agent. Claude Code's default allow-list includes
Bash(git:*), which permits any git command, including a force push; narrow it for repositories where that matters. The other agents' defaults are broader still (see Agents). - Import connection keys are stored in plain text in
<DATA_DIR>/foreman.db, which foreman keeps readable by its owner only. The console can set or replace a key but never shows one. Anyone who can read that file can read the keys. - Agents do not get import keys. They are never placed in the agent's environment, and any
LINEAR_*variable is removed from it, for the setup command too. Everything else in foreman's environment is inherited by the agent, so do not keep unrelated secrets in.env. - Foreman pushes to the task branch and nothing else. With the Git branch source, foreman commits and pushes to the task branch of the repository the project names, with the git credentials of the account it runs under; it never pushes to any other branch. An agent working on such a project has the same credentials and could reach the task branch too, so its prompt tells it to leave the task directory alone on every branch; the agent's permission settings remain the boundary. With Storage set to Local only, foreman pushes nothing at all.
Limits
- Tasks are taken by priority (urgent first, "no priority" last), then by number. Concurrency is bounded by the agents' limits, up to 16 per agent.
- Runs in the same repository set up their worktrees one after another, then work in parallel.
- No retries of failed runs. A failed task needs a person.
- A worktree is kept after a successful run if the agent left uncommitted files in it; the run log says so.
- The agent runs headless, so it cannot wait on anything interactive. A run whose agent ends its turn without opening a pull request is reported as failed, unless the agent said it needs input (see How a run works).
Development
npm ci # not npm install, which rewrites the lockfile
npm run dev # foreman run from source, reloading on change, with data in ./data
npm test # unit tests (Vitest)
npm run typecheck
npm run lintnpm run dev and npm start (the built version) keep their data, and read .env, in ./data, apart from an installed foreman's.
Releasing. Describe each user-visible change under ## [Unreleased] in CHANGELOG.md as it lands. To release, rename that section to the new version and date (## [0.3.0] - 2026-11-02), add a fresh ## [Unreleased] above it, commit, then:
npm version 0.3.0 # sets package.json and package-lock.json, commits and tags v0.3.0
git push --follow-tagsThe tag starts .github/workflows/release.yml, which checks the tag matches package.json, runs the checks, publishes to npm and creates the GitHub release from that version's changelog section.
Tests use fakes for the trackers, git and the agent CLIs (test/fixtures/); none of them touch the network.
The office's art. node tools/office-preview/preview.mjs renders the Office tab to PNG files in .office-preview/ without a browser. It plays scripted scenes against a stand-in foreman and saves contact sheets of chosen frames, so poses and animations can be checked frame by frame. --list shows the scenes, naming some renders only those, and --scale sets the pixel size. Scenes are in tools/office-preview/scenes.mjs. Text is drawn in a small bitmap font, so it is placed like the browser's but does not look like it.
The office demo. npm run office:demo serves the Office tab full screen at http://127.0.0.1:4320/, playing an endless made-up day against a stand-in foreman: tasks arrive, agents work them, runs fail or open pull requests, and pull requests are merged. It needs no foreman, tracker or agent. ?seed=<n> plays the same day every time and ?speed=<x> plays it up to twice as fast. PORT and HOST change where it listens. The day is in tools/office-preview/demo-day.mjs.
To add an import source, implement ImportType (src/imports/types.ts) in src/imports/ and register it in src/imports/registry.ts; it only reads, and the Import tab writes what it lists as task files. To add an agent, implement AgentAdapter (src/agent/types.ts) in src/agent/adapters/ and register it in src/agent/registry.ts.
