Foreman coordinates parallel OpenCode Builder agents, gates changes through an Inspector, and opens GitHub pull requests. It isolates concurrent work with git worktrees, enforces retry limits (replace stuck Builders), and notifies developers via Slack when human input is required.
This repository provides two layers:
- OpenCode-first UX: you start from OpenCode by selecting the Foreman agent and pasting a prompt.
- Python supervisor:
foreman.pyis the reliable control/data plane that owns worktrees, server processes, retries, PR creation, and persisted state.
Owns and enforces:
- task queue and task state (persisted via SQLite or similar for crash recovery)
- git worktrees/branches lifecycle
- OpenCode server process lifecycle
- retry limits and “stuck” handling
- diff/patch collection and Inspector review routing
- GitHub PR creation
- Slack notifications for human input
Works inside an isolated worktree (ideally containerized):
- edits code, runs tests, commits changes
- signals completion with
READY_FOR_REVIEW+ JSON - signals blocking questions with
NEEDS_HUMAN_INPUT+ JSON
Note: For true isolation (dependencies, runtime environment), it is recommended to run each Builder in a lightweight container (Docker/Podman) that mounts the specific worktree. This prevents environment pollution between concurrent agents.
Review-only gatekeeper:
- reviews patch/commit
- outputs only a JSON decision:
{ status, issues, next_tasks } - no edit/bash permissions
Optimization: To save AI tokens and time, Foreman runs automated "pre-flight" checks (lint, build, unit tests) before invoking the Inspector. If the code fails to compile or lint, it is rejected immediately without an expensive LLM review.
For each task:
-
Foreman creates a git worktree + unique branch.
-
Foreman starts a dedicated Builder OpenCode server rooted at the worktree.
-
Builder implements and commits.
-
Foreman produces a patch from the commit.
-
(Optional) Foreman runs pre-flight checks (lint, test). If these fail, the task is returned to the Builder immediately.
-
Inspector reviews patch and returns a JSON decision.
-
Foreman either:
- opens a GitHub PR (default draft) on approval, or
- feeds issues back to Builder and loops.
-
If Builder is stuck (default 3 review cycles), Foreman stops the Builder, deletes the worktree, and retries with a fresh Builder.
-
If human input is needed, Foreman notifies via Slack and moves the task to
WAITING_FOR_HUMANuntil answered in Foreman’s OpenCode session.
- Python 3.11+
git- OpenCode CLI (
opencode) on PATH - GitHub CLI (
gh) authenticated or a GitHub token (REST) - Slack bot token (for notifications)
httpxslack_sdk
Install:
pip install -r requirements.txt
# or
pip install httpx slack_sdk{
"instance": {
"name": "dev-A",
"state_dir": ".foreman/dev-A",
"worktrees_dir": ".oc_worktrees/dev-A"
},
"github": {
"repo": "OWNER/REPO",
"base_branch": "main",
"draft_by_default": true,
"use_gh_cli": true,
"default_labels": ["autocode"],
"default_reviewers": [],
"default_assignees": []
},
"slack": {
"enabled": true,
"notify_by": "email",
"message_prefix": "[Foreman]"
},
"limits": {
"max_review_cycles": 3,
"inspector_max_inflight_reviews": 4
}
}Notes:
instance.nameis included in Slack notifications so it’s easy to find the right Foreman when multiple instances run.state_dirandworktrees_dirmust be unique per instance to avoid collisions.- PRs are created as draft by default; override per task or set
draft_by_default=false.
GitHub:
- If using
gh: authenticate once withgh auth login. - If using REST:
GITHUB_TOKEN.
Slack:
SLACK_BOT_TOKEN
Slack app scopes typically required:
users:read.emailchat:writeim:write
[
{
"id": "feat-123",
"title": "Add pagination to widgets endpoint",
"instructions": "Implement limit/offset for GET /widgets. Update docs and tests.",
"base_ref": "main",
"assignee_email": "[email protected]",
"test_command": "pytest -q",
"draft": true
}
]Per-task overrides:
draft(overrides config default)labels,reviewers,assigneespr_title,pr_body
- Open the repository in OpenCode.
- Select the Foreman agent.
- Paste a prompt describing the work.
Example prompt:
Implement pagination for GET /widgets (limit/offset), update docs and tests. Assignee [email protected]. Run pytest -q. Create a PR.
Foreman will:
- materialize
tasks.json(or an OpenSpec change folder, if configured) - run the supervisor loop
- report status and next actions
python foreman.py run --repo /path/to/repo --config foreman.json --tasks tasks.json --concurrency 4python foreman.py status --config foreman.jsonWhen a task is WAITING_FOR_HUMAN, answer in the Foreman OpenCode session or via CLI:
python foreman.py answer --config foreman.json --task feat-123 --answer "yes"Builder must end its final message with:
- Marker line:
READY_FOR_REVIEW
- JSON object:
{ "commit": "<sha>", "summary": "...", "tests_run": ["..."], "notes": "..." }- Marker line:
NEEDS_HUMAN_INPUT
- JSON object:
{
"question": "...",
"options": ["..."],
"blocking": true,
"context": ["..."]
}Inspector must output only one JSON object:
{
"status": "approved|changes_requested",
"issues": [
{ "severity": "blocker|major|minor", "description": "...", "paths": ["..."] }
],
"next_tasks": ["..."]
}Default rule:
max_review_cycles = 3
Behavior:
- After 3
changes_requestedcycles, Foreman marks the task stuck. - Refinement (Recommended): Instead of immediately killing the Builder, Foreman triggers a
NEEDS_HUMAN_INPUTevent. The developer is asked if they want to refine the task/prompt, as 3 failures often indicate ambiguous requirements rather than a "bad" agent. - If no refinement is offered or the cycle limit is hit again, Foreman stops the Builder, deletes the worktree, and retries with a fresh Builder.
When multiple Builders run in parallel on the same repo, merge conflicts are possible.
- Mitigation: Structure tasks to be modular and orthogonal (e.g., "Agent A owns Component X, Agent B owns Component Y").
- Resolution: Foreman does not ask agents to resolve complex merge conflicts. If a conflict occurs during the PR phase or sync, the task is flagged for human intervention.
- Each Builder runs in its own git worktree and branch.
- Each Builder gets its own OpenCode server process rooted in that worktree.
- Inspector runs as a shared review service, but Foreman keeps one Inspector session per task to avoid cross-task context bleed.
On approval, Foreman:
- pushes the task branch
- creates a PR
Default: draft PR (draft_by_default=true).
PR body includes:
- task summary
- tests run
- Inspector decision metadata
- If using
gh, ensure you are authenticated:gh auth status. - If using REST, ensure
GITHUB_TOKENhas repo permissions.
- Verify
SLACK_BOT_TOKENand Slack app scopes. - Verify the task has
assignee_emailand that the email matches the Slack user profile.
- Foreman uses forced removal for stuck tasks; if a worktree directory remains, ensure no process is still holding files open.
Add your license here.