orchestrator-sh
Health Warn
- No license — Repository has no license file
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 7 GitHub stars
Code Pass
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
A lightweight bash autonomous coding agents orchestrator.
Orchestrator
A lightweight autonomous agent orchestrator that routes tasks to AI coding agents, manages their execution in isolated git worktrees, and tracks the full lifecycle from GitHub Issue to merged PR. GitHub Issues are the native task backend — labels drive status, comments carry agent output, and sub-issues handle delegation. Agents run via CLI tools (claude, codex, and opencode) in tmux sessions with full tool access.
Install
brew tap gabrielkoerich/tap
brew install orchestrator
All dependencies (yq, jq, just, python3, rg, fd) are installed automatically.
Agent CLIs
Install at least one:
brew install --cask claude-code # Claude
brew install --cask codex # Codex
brew install opencode # OpenCode
Required: gh (GitHub CLI) — the native task backend uses GitHub Issues.
Optional: bats for running tests.
Quick Start
cd ~/projects/my-app
orchestrator init # configure project + GitHub repo
orchestrator task add "title" # creates a GitHub issue
orchestrator task next # route + run next task
orchestrator start # start background server
Architecture
Tasks are GitHub Issues. The orchestrator polls for issues with status:new labels, routes them to the best agent, creates an isolated git worktree, runs the agent in a tmux session, and posts results back as issue comments. PRs are created automatically and linked via Closes #N.
GitHub Issue (status:new) → Route (LLM picks agent) → Worktree (isolated branch)
→ tmux session (agent runs) → Commit + Push → PR → Review → Merge
Files
All runtime state lives in ~/.orchestrator/ (ORCH_HOME):
config.yml— global runtime configurationprojects.yml— registered projects for multi-project pollingskills.yml— approved skill repositories and catalogskills/— cloned skill repositories (viaskills-sync)projects/— bare-cloned repositories (owner/repo.git)worktrees/— project-local git worktrees per task.orchestrator/— runtime state (pid, logs, locks, sidecars, prompts)
Per-project files (in each project root):
orchestrator.yml— project config (GitHub repo, project ID).orchestrator/jobs.yml— scheduled job definitions.orchestrator/— project runtime state (output, prompts, locks)
Source files:
prompts/system.md— system prompt (output format, workflow, constraints)prompts/agent.md— execution prompt (task details + enriched context)prompts/plan.md— planning/decomposition promptprompts/route.md— routing + profile generation promptprompts/review.md— optional review agent promptscripts/backend_github.sh— GitHub Issues backend (labels, comments, status)scripts/run_task.sh— task execution (routing, agent invocation, response parsing)scripts/serve.sh— main service loopscripts/poll.sh— task dispatchertests/orchestrator.bats— 265+ tests.orchestrator.example.yml— template for per-project config override
Task Model
Each task is a GitHub Issue. Metadata is stored in labels and a local sidecar JSON file:
GitHub Issue labels (source of truth):
status:new,status:routed,status:in_progress,status:done,status:blocked,status:in_review,status:needs_reviewagent:claude,agent:codex,agent:opencodecomplexity:simple,complexity:medium,complexity:complexrole:backend,role:frontend,role:docs, etc.plan,scheduled,no-agent,no-review,has-error
Sidecar file (~/.orchestrator/.orchestrator/{task_id}.json):
agent_model,complexity,branch,worktreeattempts,duration,input_tokens,output_tokenssummary,reason,accomplished[],remaining[],files_changed[]prompt_hash,last_comment_hash
Issue comments serve as history (timestamped status transitions, agent reports).
How It Works
- Create a GitHub Issue (or via
orchestrator task add). Getsstatus:newlabel automatically. - Route the task — LLM router picks the best agent + model and builds a specialized profile.
- Create worktree — isolated git branch + worktree at
~/.orchestrator/worktrees/{project}/{branch}. - Run the agent in a tmux session (
orch-{issue_number}). Agent has full tool access inside the worktree. - Collect results — agent writes JSON to output file. Orchestrator parses, commits changes, pushes branch, creates PR.
- Review — if enabled, a different agent reviews the PR and posts a GitHub review.
- Delegation — if the agent returns
delegations, child issues are created as sub-issues and the parent is blocked until children finish. - Error handling — failures are posted as issue comments with full context.
status:blockedlabel added./retrycommand to re-run.
Routing: How the Orchestrator Picks an Agent
The orchestrator uses an LLM-as-classifier to route each task to the best agent. This is a non-agentic call (--print) — fast and cheap, no tool access needed.
How It Works
route_task.shsends the task title, body, labels, and the skills catalog to a lightweight LLM (default:claude --model haiku --print).- The router LLM returns JSON with:
- executor: which agent to use (
codex,claude, oropencode) - model: optional model suggestion (e.g.
sonnet,opus,gpt-4.1) - reason: short explanation of the routing decision
- profile: a specialized agent profile (role, skills, tools, constraints)
- selected_skills: skill ids from the catalog
- executor: which agent to use (
- Sanity checks run — e.g. warns if a backend task gets routed to claude, or a docs task to codex.
- If the router fails, it falls back to
config.yml'srouter.fallback_executor(default:codex).
Router Config
router:
agent: "claude" # which LLM does the routing
model: "haiku" # fast/cheap model for classification
timeout_seconds: 120
fallback_executor: "codex" # safety net if routing fails
Available Executors
| Executor | Best for |
|---|---|
codex |
Coding, repo changes, automation, tooling |
claude |
Analysis, synthesis, planning, writing |
opencode |
Lightweight coding and quick iterations |
The routing prompt is in prompts/route.md. The router only classifies — it never touches code or files.
Agentic Mode
Once routed, agents run in full agentic mode with tool access:
- Claude:
-pflag (non-interactive agentic mode),--permission-mode acceptEdits,--output-format json, system prompt via--append-system-prompt - Codex:
-qflag (quiet non-interactive mode),--json, system+agent prompt combined - OpenCode:
opencode run --format jsonwith combined prompt
Agents execute inside $PROJECT_DIR (the directory you ran orchestrator from), so they can read project files, edit code, and run commands. The context below is injected into the prompt as starting knowledge so agents don't waste time exploring — but since they have full tool access, they can also read any file themselves.
Agent Safety Rules
Agents are constrained by rules in the system prompt:
- No
rm:--disallowedToolsblocksrm— agents must usetrash(macOS) ortrash-put(Linux) - No commits to main: Agents must always work in feature branches
- Git identity: Commits are authored as
{agent}[bot](e.g.claude[bot],opencode[bot]) so you can see which agent made each commit. Configurable viagit.name/git.emailinconfig.yml. - Required skills: Skills listed in
workflow.required_skillsare marked[REQUIRED]in the agent prompt and must be followed exactly - GitHub issue linking: If a task has a linked issue, the agent receives the issue reference for branch naming and PR linking
- Cost-conscious sub-agents: Agents are instructed to use cheap models for routine sub-agent work
Context Enrichment
Every agent receives a rich context built from multiple sources:
| Context | Source | When | Description |
|---|---|---|---|
| System prompt | prompts/system.md |
Always | Output format, JSON schema, workflow requirements, constraints |
| Task details | GitHub Issue | Always | Title, body, labels, agent profile (role/skills/tools/constraints) |
| Error history | Issue comments | On retries | Last 5 status transitions with timestamps (agent sees what already failed) |
| Last error | Sidecar JSON | On retries | Most recent error message |
| GitHub issue comments | GitHub API | If issue linked | Last 10 comments on the linked issue (agent sees discussion) |
| Prior run context | contexts/task-{id}.md |
On retries | Logs from previous attempts + tool call summaries |
| Repo tree | git ls-files / find |
Always | Truncated file listing (up to 200 files) |
| Project instructions | CLAUDE.md + AGENTS.md + README.md |
If files exist | Project-specific instructions and documentation |
| Skills docs | skills/{id}/SKILL.md |
If skills selected | Full skill documentation for each selected skill |
| Parent context | Parent issue | For child tasks | Parent task summary + sibling task statuses |
| Git diff | git diff --stat HEAD |
On retries (attempts > 0) | Current uncommitted changes |
| Output file path | .orchestrator/output-{id}.json |
Always | Where the agent writes its JSON results |
How Context Flows
run_task.sh
├── load_task() → TASK_TITLE, TASK_BODY, TASK_LABELS, AGENT_PROFILE_JSON, ...
├── fetch_issue_comments() → ISSUE_COMMENTS (last 10 GitHub issue comments)
├── task history + last_error → TASK_HISTORY, TASK_LAST_ERROR
├── load_task_context() → TASK_CONTEXT (prior run logs + tool summaries)
├── build_parent_context() → PARENT_CONTEXT (parent summary + sibling statuses)
├── build_project_instructions() → PROJECT_INSTRUCTIONS (CLAUDE.md + AGENTS.md + README.md)
├── build_skills_docs() → SKILLS_DOCS (SKILL.md for each selected skill)
├── build_repo_tree() → REPO_TREE (truncated file listing)
├── build_git_diff() → GIT_DIFF (on retries only)
│
├── render_template("prompts/system.md") → SYSTEM_PROMPT (workflow + output format)
├── render_template("prompts/agent.md") → AGENT_MESSAGE (all context above)
│
├── agent invocation (cd $PROJECT_DIR)
│ ├── claude -p --permission-mode acceptEdits --output-format json ...
│ ├── codex -q --json ...
│ └── opencode run --format json ...
│
└── post-agent
├── extract tool history → tools-{id}.json
├── extract token usage → input_tokens, output_tokens
├── capture stderr snippet → stderr_snippet (500 chars)
├── calculate duration → duration (seconds)
├── push branch if not main → git push -u origin <branch>
└── store metadata in sidecar + issue labels
Output
The agent writes results to .orchestrator/output-{task_id}.json. If the file isn't found (e.g. older agents or non-agentic fallback), the orchestrator falls back to parsing JSON from stdout via normalize_json.py, which handles Claude's result envelope, markdown fences, and mixed text.
Usage
| Command | Description |
|---|---|
orchestrator init |
Initialize orchestrator for current project. |
orchestrator dashboard |
Overview: tasks, projects, worktrees. |
orchestrator chat |
Interactive chat with the orchestrator. |
orchestrator start |
Start server (uses brew services if installed via brew). |
orchestrator stop |
Stop server. |
orchestrator log |
Tail orchestrator log. |
orchestrator agents |
List installed agent CLIs. |
orchestrator --version |
Show version. |
Task Commands
| Command | Description |
|---|---|
orchestrator task status |
Show status counts and recent tasks. |
orchestrator task status -g |
Show global status across all projects. |
orchestrator task add "Build router" "Add LLM router" "orchestration" |
Add a task (title required, body/labels optional). |
orchestrator task plan "Implement auth" "Add login, signup, reset" "backend" |
Add a task that will be decomposed into subtasks first. |
orchestrator task list |
List tasks (id, status, agent, parent, title). |
orchestrator task tree |
Show parent/child task tree. |
orchestrator task route 1 |
Route task 1. If no ID, route next new task. |
orchestrator task run 1 |
Run task 1. If no ID, run next runnable. |
orchestrator task next |
Route + run the next task in one step. |
orchestrator task poll |
Run all runnable tasks in parallel (default 4 workers). |
orchestrator task retry 1 |
Retry a blocked/done task (reset to new). |
orchestrator task unblock 1 |
Unblock a blocked task (reset to new). |
orchestrator task unblock all |
Unblock all blocked tasks. |
orchestrator task agent 1 claude |
Force a task to use a specific agent. |
orchestrator task stream 1 |
Stream live agent output. |
orchestrator task watch |
Poll loop every 10s. |
Scheduled Jobs
| Command | Description |
|---|---|
orchestrator job add "0 9 * * *" "Daily Sync" "Pull and check" "sync" |
Add a scheduled task job. |
orchestrator job add --type bash --command "echo hi" "@hourly" "Ping" |
Add a bash job (no LLM). |
orchestrator job list |
List all jobs with status and next run. |
orchestrator job remove daily-sync |
Remove a job. |
orchestrator job enable daily-sync |
Enable a job. |
orchestrator job disable daily-sync |
Disable a job. |
Skills
| Command | Description |
|---|---|
orchestrator skills list |
List skills in the catalog. |
orchestrator skills sync |
Sync skills from registry to skills/. |
Per-Project Isolation
Each project is initialized separately and registered in a global project registry:
cd ~/projects/app-a && orchestrator init # registers project
cd ~/projects/app-b && orchestrator init # registers project
orchestrator init does three things:
- Creates
orchestrator.ymlin the project root (GitHub repo, project ID) - Creates
.orchestrator/state directory - Registers the project in
~/.orchestrator/projects.yml
The registry file (~/.orchestrator/projects.yml) tracks all managed projects:
projects:
- name: app-a
path: /Users/you/projects/app-a
- name: app-b
path: /Users/you/projects/app-b
A single orchestrator serve polls all registered projects. Each project gets its own GitHub repo context, labels, and task isolation:
cd ~/projects/app-a && orchestrator task list # shows only app-a tasks
cd ~/projects/app-b && orchestrator task list # shows only app-b tasks
Use orchestrator dashboard to see active projects and worktrees.
Background Service
orchestrator start # start (delegates to brew services)
orchestrator stop # stop
orchestrator restart # restart
orchestrator info # check status
Auto-starts on login, auto-restarts on crash via brew services.
Scheduled Jobs (Cron)
Jobs are defined per-project in .orchestrator/jobs.yml and create regular tasks on a schedule. They flow through the full pipeline (route, run, review, delegate, GitHub sync).
How It Works
- Define a job with a cron expression and a task template.
- On each tick, the scheduler checks which jobs are due.
- Each job tracks its
active_task_id. If that task is still in-flight (any status exceptdone), the job waits — no duplicates. - When the previous task completes, the job is free to create a new one on the next matching schedule.
Running the Scheduler
The server runs job tick on every poll cycle automatically:
orchestrator start
Job Definition
Two job types:
- task (default): creates a task that goes through routing and agent execution
- bash: runs a shell command directly, no LLM involved
# .orchestrator/jobs.yml (per-project)
jobs:
- id: daily-sync
schedule: "0 9 * * *"
type: task # default, creates agent task
task:
title: "Daily code sync"
body: "Pull latest changes, run linting, check for issues"
labels: [sync]
agent: "" # empty = let router decide
enabled: true
last_run: null
last_task_status: null
active_task_id: null
- id: hourly-ping
schedule: "@hourly"
type: bash # runs command directly
command: "curl -s https://example.com/health"
enabled: true
Schedule Expressions
Standard 5-field cron: minute hour day_of_month month day_of_week
Aliases: @hourly, @daily, @weekly, @monthly, @yearly
Supports: wildcards (*), ranges (1-5), steps (*/15, 1-5/2), lists (1,3,5).
Dedup & Safety
- Each job has
active_task_idtracking its current in-flight task. - If the task is
new,routed,in_progress, orblocked— the job waits. - Tasks can delegate children, get reviewed, or get blocked — the job won't interfere.
- Only when the task reaches
donedoes the job create a new one. - Tasks from jobs get
scheduledandjob:{id}labels for easy filtering. - All job-created tasks sync to GitHub issues like any other task.
Dynamic Agent Profiles
The router generates a profile for each task, stored in the sidecar file. You can override routing via issue labels.
Example:
agent_profile:
role: backend specialist
skills: [api, sql, testing]
tools: [git, rg]
constraints: ["no migrations"]
Skills Catalog
skills.yml defines approved skill repositories and a catalog of skills. The router selects skill ids and stores them in selected_skills.
Required Skills
Skills listed in workflow.required_skills are always injected into agent prompts, marked [REQUIRED], and enforced regardless of what the router selects. Configure in config.yml:
workflow:
required_skills:
- github # GitHub CLI operations
- gh-pr-polish # PR titles and bodies
Commit Pinning
Skill repositories are pinned to audited commit SHAs in skills.yml to prevent supply chain attacks:
repositories:
- name: gabrielkoerich
url: https://github.com/gabrielkoerich/skills
pin: 226f5f11346eddceebf017746aa5cd660ef3af20
When pinned, skills-sync checks out the exact commit instead of pulling latest.
Syncing
Clone or update skills with:
orchestrator skills sync
Error Handling & GitHub Issue Feedback
When an agent fails, the orchestrator classifies the error and blocks the task:
Error Classification
| Error Type | Detection | Action |
|---|---|---|
| Auth/billing | Pattern match on stderr/stdout (401, 403, expired key, quota, etc.) | Block + comment on issue |
| Timeout | Exit code 124 | Block + comment on issue |
| Generic failure | Any non-zero exit | Block + comment on issue |
| Invalid response | No JSON in output file or stdout | Block + comment on issue |
Retry Loop Detection
If the same error repeats 3 times (4+ attempts), the orchestrator detects a retry loop and blocks the task permanently with a clear message.
What Happens on Failure
- Task status set to
blocked(no auto-retry) - Error details saved to sidecar file and posted as issue comment
- Error logged to task history with timestamp
- GitHub issue comment posted with full details (see below)
- Red
blockedlabel added to the GitHub issue (auto-created if missing)
Unblocking
Tasks stay blocked until you manually investigate and unblock them:
- Check the error on the GitHub issue
- Fix the underlying problem (e.g. add API key, fix code)
- Remove the
blockedlabel from the issue - Set the task status back to
new— the orchestrator picks it up again
Agent-Reported Blocks
Agents can also report blocks in their JSON response:
status: blocked— waiting for a dependency or missing information- The agent must provide a
reasonfield explaining what happened, what it tried, and what it needs
The reason is posted as an issue comment, logged to the sidecar, and appended to contexts/task-{id}.md.
Logging & Observability
All state lives under ~/.orchestrator (the install dir). Logs and runtime files are in the .orchestrator/ subdirectory within it.
Log Files
| File | Path | Description |
|---|---|---|
| Server log | .orchestrator/orchestrator.log |
Main loop output: ticks, poll, gh sync, restarts |
| Archive log | .orchestrator/orchestrator.archive.log |
Previous server sessions (rotated on each start) |
| Jobs log | .orchestrator/jobs.log |
Scheduled job execution: triggers, task creation, bash output |
| Brew log | /opt/homebrew/var/log/orchestrator.log |
stdout when running via brew services |
View the server log:
orchestrator log # tail last 50 lines
orchestrator log 200 # tail last 200 lines
Or tail it live while the server runs:
TAIL_LOG=1 orchestrator serve
Per-Task Logs
| File | Path | Description |
|---|---|---|
| Task context | contexts/task-{id}.md |
Appended after each run: timestamp, status, summary, reason, files, tool summary |
| Task history | Issue comments | Status transitions with timestamps and notes |
| Agent output | .orchestrator/output-{id}.json |
Structured JSON from the last agent run |
| Agent prompt | .orchestrator/prompt-{id}.txt |
Full system prompt + agent message (with SHA-256 hash) |
| Agent response | .orchestrator/response-{id}.txt |
Raw stdout from the agent |
| Agent stderr | .orchestrator/stderr-{id}.txt |
Stderr captured from the agent (auth errors, warnings) |
| Tool history | .orchestrator/tools-{id}.json |
Every tool call the agent made (Bash, Edit, Read, etc.) with error flags |
| Route prompt | .orchestrator/route-prompt-{id}.txt |
The prompt sent to the router (for debugging routing decisions) |
| Failed response | contexts/response-{id}.md |
Raw agent output when JSON parsing fails |
GitHub Issue Comments
When GitHub sync is enabled, each status update posts a structured comment on the linked issue:
## 🟣 Claude Fixed the authentication bug ← agent badge + summary as title
| | |
|---|---|
| **Status** | `done` | ← metadata table
| **Agent** | claude |
| **Model** | `claude-sonnet-4-5-20250929` |
| **Attempt** | 2 |
| **Duration** | 3m 42s |
| **Tokens** | 15k in / 3k out |
| **Prompt** | `a1b2c3d4` |
### Errors & Blockers ← only when blocked
**Reason:** SSH key not configured
> `git push: Permission denied (publickey)`
- Need SSH key configured for git push
### Accomplished ← bullet list
- Fixed memcmp offset from 40 to 48
- Added test coverage for edge case
### Remaining
- Deploy to staging
### Files Changed
- `src/auth.ts`
- `tests/auth.test.ts`
### Agent Activity ← tool call summary
| Tool | Calls |
|------|-------|
| Bash | 12 |
| Edit | 5 |
| Read | 8 |
<details><summary>Agent stderr</summary> ← collapsed
...
</details>
<details><summary>Prompt sent to agent</summary> ← collapsed
...
</details>
Features:
- Agent badges: 🟣 Claude, 🟢 Codex, 🔵 OpenCode
- Content-hash dedup: identical comments are not re-posted (prevents spam)
- Atomic sync:
gh_synced_atcopiesupdated_atat write time (no stale variable bugs) - Blocked label: red
blockedlabel auto-applied/removed based on status - Owner ping:
@ownertagged on blocked tasks
What Gets Logged Where
| Event | Server log | Task context | Task history | GitHub comment | Per-task files |
|---|---|---|---|---|---|
| Tick/poll cycle | x | ||||
| Task started | x | x | prompt saved | ||
| Agent completed | x (duration, tokens, tools) | x (tool summary) | x | x (full report) | response + stderr + tools |
| Agent blocked/stuck | x | x | x | x (comment + label) | response + stderr + tools |
| Auth/billing error | x | x | x (comment + label) | stderr | |
| Timeout | x | x | x (comment + label) | ||
| Invalid response | x | x (raw saved) | x | x (comment + label) | response |
| Retry loop (3x same error) | x | x | x (comment + label) | ||
| Review result | x | x | x | ||
| Delegation | x | x | x | ||
| Job triggered | x | ||||
| Config/code restart | x |
Per-Project Config
Place a .orchestrator.yml in your project root to override the global config for that project. Only include the keys you want to override — everything else falls through to ~/.orchestrator/config.yml.
# myproject/.orchestrator.yml
required_tools: ["bun"]
gh:
repo: "myorg/myproject"
project_id: "PVT_..."
sync_label: ""
workflow:
auto_close: false
review_owner: "@myhandle"
router:
model: "sonnet"
This lets you:
- Use a different GitHub repo/project per project
- Customize workflow settings (review, auto-close) per project
- Override the router model or fallback agent
- Keep project-specific config in version control
The server restarts automatically when .orchestrator.yml changes.
Config Reference
All runtime configuration lives in config.yml.
| Section | Key | Description | Default |
|---|---|---|---|
| top-level | project_dir |
Override project directory (auto-detected from CWD). | "" |
| top-level | required_tools |
Tools that must exist on PATH before launching an agent. | [] |
workflow |
auto_close |
Auto-close GitHub issues when tasks are done. |
true |
workflow |
review_owner |
GitHub handle to tag when review is needed. | @owner |
workflow |
enable_review_agent |
Run a review agent after completion. | false |
workflow |
review_agent |
Fallback reviewer when opposite agent unavailable. | claude |
workflow |
max_attempts |
Max attempts before marking task as blocked. | 10 |
workflow |
timeout_seconds |
Task execution timeout (0 disables timeout). | 1800 |
workflow |
timeout_by_complexity |
Per-complexity task timeouts (takes precedence). | {} |
workflow |
required_skills |
Skills always injected into agent prompts (marked [REQUIRED]). |
[] |
workflow |
disallowed_tools |
Tool patterns blocked via --disallowedTools. |
["Bash(rm *)","Bash(rm -*)"] |
router |
agent |
Default router executor. | claude |
router |
model |
Router model name. | haiku |
router |
timeout_seconds |
Router timeout (0 disables timeout). | 120 |
router |
disabled_agents |
Agents to exclude from routing (e.g. [opencode]). |
[] |
router |
fallback_executor |
Fallback executor when router fails. | codex |
router |
allowed_tools |
Default tool allowlist used in routing prompts. | [yq, jq, bash, ...] |
router |
default_skills |
Skills always included in routing. | [gh, git-worktree] |
llm |
input_format |
CLI input format override. | "" |
llm |
output_format |
CLI output format override. | "json" |
gh |
enabled |
Enable GitHub sync. | true |
gh |
repo |
Default repo (owner/repo). |
"owner/repo" |
gh |
sync_label |
Only sync tasks/issues with this label (empty = all). | "sync" |
gh |
project_id |
GitHub Project v2 ID. | "" |
gh |
project_status_field_id |
Status field ID in Project v2. | "" |
gh |
project_status_names |
Mapping for backlog/in_progress/review/done status option names (used to resolve option IDs). |
{} |
gh |
project_status_map |
Mapping for backlog/in_progress/review/done option IDs. |
{} |
gh.backoff |
mode |
Rate-limit behavior: wait or skip. |
"wait" |
gh.backoff |
base_seconds |
Initial backoff duration in seconds. | 30 |
gh.backoff |
max_seconds |
Max backoff duration in seconds. | 900 |
Context Persistence
Task and profile contexts are persisted under contexts/:
contexts/task-<id>.md— logs from each run (status, summary, reason, files)contexts/profile-<role>.md— role-specific context
The orchestrator loads both into the prompt and appends a log entry after each run.
Task Decomposition (Plan Mode)
Complex tasks can be broken down into smaller subtasks before execution. This happens in two ways:
Automatic (router decides)
The router evaluates task complexity and sets decompose: true when a task touches multiple systems, requires many file changes, or has multiple deliverables. The task gets a plan label automatically.
Manual (user decides)
Add the plan label when creating a task:
orchestrator task plan "Implement user auth" "Add login, signup, password reset with JWT tokens" "backend"
Or add a task with the plan label directly:
orchestrator task add "Redesign the API" "..." "plan,backend"
How It Works
- The agent receives
prompts/plan.mdinstead of the execution prompt - It reads the codebase, analyzes the task, and returns only delegations (no code changes)
- Each subtask gets a clear title, detailed body with acceptance criteria, labels for routing, and a suggested agent
- The parent blocks until all children complete, then resumes with the execution prompt
Guidelines in the planning prompt
- Each subtask should be completable in a single agent run
- Subtasks are listed in dependency order
- Bodies include specific file paths, function names, and expected behavior
- Prefers 3-7 subtasks (not too granular, not too broad)
- Includes a testing/verification subtask at the end
Delegation
If the agent returns this:
{
"needs_help": true,
"delegations": [
{
"title": "Add unit tests",
"body": "Test routing logic",
"labels": ["tests"],
"suggested_agent": "codex"
}
]
}
The orchestrator will:
- Create child tasks
- Block the parent until children are done
- Re-run the parent via
pollorrejoin
Concurrency + Locking
pollruns new tasks in parallel.- SQLite WAL mode handles concurrent reads; per-task locks prevent double-run.
- Each task also has a per-task lock to prevent double-run.
- Stale locks are auto-cleared after
LOCK_STALE_SECONDS(default 600).
Review Agent (Optional)
Automatically review PRs using a different agent from the one that wrote the code.
workflow:
enable_review_agent: true
When enabled, after an agent completes a task and a PR is open:
- Opposite agent selected — if codex wrote the code, claude reviews (and vice versa). Falls back to
workflow.review_agentconfig if only one agent is available. - PR diff fetched — the actual PR diff via
gh pr diff(first 500 lines). - Real GitHub review posted — via
gh pr review:approve→ green checkmark review on the PRrequest_changes→ red X review, task goes toneeds_reviewreject→ review + PR closed, task goes toneeds_review
The review_agent config key is now an optional fallback — opposite_agent() handles primary selection.
Override the reviewer for a specific run:
REVIEW_AGENT=claude orchestrator task run <id>
See Review Agent docs for full details.
GitHub Setup
GitHub Issues is the native task backend. Authentication is handled by gh.
Recommended: Fine-grained PAT scoped to the target repo(s).
Repository permissions:
- Issues: Read + Write
- Metadata: Read
- Contents: Read (optional)
Organization permissions (for Projects v2):
- Projects: Read + Write
Classic PATs will also work but are broader in scope.
GitHub Setup
- Install and authenticate:
gh auth login
- Verify access:
gh repo view
Project fields belong in config.yml:
gh:
project_id: ""
project_status_field_id: ""
project_status_map:
backlog: ""
in_progress: ""
review: ""
done: ""
To discover Project field and option IDs:
orchestrator project info
To auto-fill the Status field/options into config:
orchestrator project info --fix
Manual sync commands
orchestrator gh pull # import new issues as tasks
orchestrator gh push # push local status updates to GitHub
orchestrator gh sync # both directions
Note: The background service runs gh sync automatically every 120s.
Error Comments & Blocking
When a task fails (any error), the orchestrator:
- Posts a structured comment on the linked GitHub issue (see "GitHub Issue Comments" above)
- Adds a red
blockedlabel to the issue (auto-created if missing) - Sets the task status to
blocked - Tags
@ownerfor attention
Comments include: agent badge, status metadata table (agent, model, attempt, duration, tokens), error details, blockers, tool activity summary, and collapsed stderr/prompt. Content-hash dedup prevents duplicate comments on repeated syncs.
Notes
- The repo is resolved from
config.ymlorgh repo view. - Issues are created for tasks without
gh_issue_number. - If a task has label
no_ghorlocal-only, it will not be synced. - If
config.ymlgh.sync_labelis set, only tasks/issues with that label are synced. - If
config.ymlgh.enabledisfalse, GitHub sync is disabled. - Task status is synced to issue labels using
status:<status>. - When a task is
done,auto_closecontrols whether to close the issue or tag the owner for review. - Scheduled job tasks get
scheduledandjob:{id}labels and sync to GitHub like any other task. - Agents never call GitHub directly; the orchestrator posts comments and status updates so it can back off safely when rate-limited.
GitHub Backoff
When GitHub rate limits or abuse detection triggers, the orchestrator sleeps and retries instead of hammering the API.
Config keys:
gh.backoff.mode—wait(default) orskipgh.backoff.base_seconds— initial backoff durationgh.backoff.max_seconds— maximum backoff duration
The backoff is shared across pull/push/comment/project updates, so a single rate limit event pauses all GitHub writes.
Projects (Optional)
Provide in config.yml:
gh.project_idgh.project_status_field_idgh.project_status_map(Backlog/In Progress/Review/Done option IDs)
Finding IDs
- Project ID (GraphQL):
gh api graphql -f query='query($org:String!, $num:Int!){ organization(login:$org){ projectV2(number:$num){ id } } }' -f org=YOUR_ORG -f num=PROJECT_NUMBER
- Status field ID + option IDs (or use
orchestrator project info):
gh api graphql -f query='query($project:ID!){ node(id:$project){ ... on ProjectV2 { fields(first:50){ nodes{ ... on ProjectV2SingleSelectField { id name options{ id name } } } } } } }' -f project=YOUR_PROJECT_ID
Notes
- GitHub Issues are the source of truth for tasks. Labels drive status.
- Routing and profiles are LLM-generated; you can override them via issue labels.
- Agents run in agentic mode inside isolated git worktrees with full tool access.
- The router stays non-agentic (
--print) — it's a classification task.
Development
git clone https://github.com/gabrielkoerich/orchestrator.git
cd orchestrator
bats tests # run tests
just # list available commands
Requires: yq, jq, just, python3, rg, fd, bats.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found