{"id":44417174,"url":"https://github.com/gabrielkoerich/orchestrator","last_synced_at":"2026-03-01T17:08:51.678Z","repository":{"id":336846610,"uuid":"1151027771","full_name":"gabrielkoerich/orchestrator","owner":"gabrielkoerich","description":"A lightweight bash autonomous coding agents orchestrator.","archived":false,"fork":false,"pushed_at":"2026-02-17T14:05:15.000Z","size":348,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-17T16:44:07.006Z","etag":null,"topics":["claude","claude-code","codex","opencode","orchestrator"],"latest_commit_sha":null,"homepage":"http://projects.gabrielkoerich.com/orchestrator/","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gabrielkoerich.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-06T01:00:40.000Z","updated_at":"2026-02-17T14:04:04.000Z","dependencies_parsed_at":"2026-02-15T12:01:19.496Z","dependency_job_id":null,"html_url":"https://github.com/gabrielkoerich/orchestrator","commit_stats":null,"previous_names":["gabrielkoerich/orchestrator"],"tags_count":111,"template":false,"template_full_name":null,"purl":"pkg:github/gabrielkoerich/orchestrator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielkoerich%2Forchestrator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielkoerich%2Forchestrator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielkoerich%2Forchestrator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielkoerich%2Forchestrator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gabrielkoerich","download_url":"https://codeload.github.com/gabrielkoerich/orchestrator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielkoerich%2Forchestrator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29627664,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T18:02:07.722Z","status":"ssl_error","status_checked_at":"2026-02-19T18:01:46.144Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude","claude-code","codex","opencode","orchestrator"],"created_at":"2026-02-12T08:37:23.907Z","updated_at":"2026-03-01T17:08:51.669Z","avatar_url":"https://github.com/gabrielkoerich.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Orchestrator\n\n[![CI](https://github.com/gabrielkoerich/orchestrator/actions/workflows/release.yml/badge.svg?branch=main)](https://github.com/gabrielkoerich/orchestrator/actions/workflows/release.yml?query=branch%3Amain)\n\nA 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.\n\n## Install\n\n```bash\nbrew tap gabrielkoerich/tap\nbrew install orchestrator\n```\n\nAll dependencies (`yq`, `jq`, `just`, `python3`, `rg`, `fd`) are installed automatically.\n\n### Agent CLIs\n\nInstall at least one:\n```bash\nbrew install --cask claude-code   # Claude\nbrew install --cask codex         # Codex\nbrew install opencode             # OpenCode\n```\n\nRequired: `gh` (GitHub CLI) — the native task backend uses GitHub Issues.\nOptional: `bats` for running tests.\n\n## Quick Start\n```bash\ncd ~/projects/my-app\norchestrator init               # configure project + GitHub repo\norchestrator task add \"title\"   # creates a GitHub issue\norchestrator task next          # route + run next task\norchestrator start              # start background server\n```\n\n## Architecture\n\nTasks 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`.\n\n```\nGitHub Issue (status:new) → Route (LLM picks agent) → Worktree (isolated branch)\n    → tmux session (agent runs) → Commit + Push → PR → Review → Merge\n```\n\n## Files\n\nAll runtime state lives in `~/.orchestrator/` (`ORCH_HOME`):\n- `config.yml` — global runtime configuration\n- `projects.yml` — registered projects for multi-project polling\n- `skills.yml` — approved skill repositories and catalog\n- `skills/` — cloned skill repositories (via `skills-sync`)\n- `projects/` — bare-cloned repositories (`owner/repo.git`)\n- `worktrees/` — project-local git worktrees per task\n- `.orchestrator/` — runtime state (pid, logs, locks, sidecars, prompts)\n\nPer-project files (in each project root):\n- `orchestrator.yml` — project config (GitHub repo, project ID)\n- `.orchestrator/jobs.yml` — scheduled job definitions\n- `.orchestrator/` — project runtime state (output, prompts, locks)\n\nSource files:\n- `prompts/system.md` — system prompt (output format, workflow, constraints)\n- `prompts/agent.md` — execution prompt (task details + enriched context)\n- `prompts/plan.md` — planning/decomposition prompt\n- `prompts/route.md` — routing + profile generation prompt\n- `prompts/review.md` — optional review agent prompt\n- `scripts/backend_github.sh` — GitHub Issues backend (labels, comments, status)\n- `scripts/run_task.sh` — task execution (routing, agent invocation, response parsing)\n- `scripts/serve.sh` — main service loop\n- `scripts/poll.sh` — task dispatcher\n- `tests/orchestrator.bats` — 265+ tests\n- `.orchestrator.example.yml` — template for per-project config override\n\n## Task Model\n\nEach task **is** a GitHub Issue. Metadata is stored in labels and a local sidecar JSON file:\n\n**GitHub Issue labels** (source of truth):\n- `status:new`, `status:routed`, `status:in_progress`, `status:done`, `status:blocked`, `status:in_review`, `status:needs_review`\n- `agent:claude`, `agent:codex`, `agent:opencode`\n- `complexity:simple`, `complexity:medium`, `complexity:complex`\n- `role:backend`, `role:frontend`, `role:docs`, etc.\n- `plan`, `scheduled`, `no-agent`, `no-review`, `has-error`\n\n**Sidecar file** (`~/.orchestrator/.orchestrator/{task_id}.json`):\n- `agent_model`, `complexity`, `branch`, `worktree`\n- `attempts`, `duration`, `input_tokens`, `output_tokens`\n- `summary`, `reason`, `accomplished[]`, `remaining[]`, `files_changed[]`\n- `prompt_hash`, `last_comment_hash`\n\n**Issue comments** serve as history (timestamped status transitions, agent reports).\n\n## How It Works\n1. **Create a GitHub Issue** (or via `orchestrator task add`). Gets `status:new` label automatically.\n2. **Route the task** — LLM router picks the best agent + model and builds a specialized profile.\n3. **Create worktree** — isolated git branch + worktree at `~/.orchestrator/worktrees/{project}/{branch}`.\n4. **Run the agent** in a tmux session (`orch-{issue_number}`). Agent has full tool access inside the worktree.\n5. **Collect results** — agent writes JSON to output file. Orchestrator parses, commits changes, pushes branch, creates PR.\n6. **Review** — if enabled, a different agent reviews the PR and posts a GitHub review.\n7. **Delegation** — if the agent returns `delegations`, child issues are created as sub-issues and the parent is blocked until children finish.\n8. **Error handling** — failures are posted as issue comments with full context. `status:blocked` label added. `/retry` command to re-run.\n\n## Routing: How the Orchestrator Picks an Agent\n\nThe 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.\n\n### How It Works\n1. `route_task.sh` sends the task title, body, labels, and the skills catalog to a lightweight LLM (default: `claude --model haiku --print`).\n2. The router LLM returns JSON with:\n   - **executor**: which agent to use (`codex`, `claude`, or `opencode`)\n   - **model**: optional model suggestion (e.g. `sonnet`, `opus`, `gpt-4.1`)\n   - **reason**: short explanation of the routing decision\n   - **profile**: a specialized agent profile (role, skills, tools, constraints)\n   - **selected_skills**: skill ids from the catalog\n3. Sanity checks run — e.g. warns if a backend task gets routed to claude, or a docs task to codex.\n4. If the router fails, it falls back to `config.yml`'s `router.fallback_executor` (default: `codex`).\n\n### Router Config\n```yaml\nrouter:\n  agent: \"claude\"       # which LLM does the routing\n  model: \"haiku\"        # fast/cheap model for classification\n  timeout_seconds: 120\n  fallback_executor: \"codex\"  # safety net if routing fails\n```\n\n### Available Executors\n| Executor | Best for |\n|---|---|\n| `codex` | Coding, repo changes, automation, tooling |\n| `claude` | Analysis, synthesis, planning, writing |\n| `opencode` | Lightweight coding and quick iterations |\n\nThe routing prompt is in `prompts/route.md`. The router only classifies — it never touches code or files.\n\n## Agentic Mode\n\nOnce routed, agents run in full agentic mode with tool access:\n- **Claude**: `-p` flag (non-interactive agentic mode), `--permission-mode acceptEdits`, `--output-format json`, system prompt via `--append-system-prompt`\n- **Codex**: `-q` flag (quiet non-interactive mode), `--json`, system+agent prompt combined\n- **OpenCode**: `opencode run --format json` with combined prompt\n\nAgents 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.\n\n### Agent Safety Rules\n\nAgents are constrained by rules in the system prompt:\n- **No `rm`**: `--disallowedTools` blocks `rm` — agents must use `trash` (macOS) or `trash-put` (Linux)\n- **No commits to main**: Agents must always work in feature branches\n- **Git identity**: Commits are authored as `{agent}[bot]` (e.g. `claude[bot]`, `opencode[bot]`) so you can see which agent made each commit. Configurable via `git.name` / `git.email` in `config.yml`.\n- **Required skills**: Skills listed in `workflow.required_skills` are marked `[REQUIRED]` in the agent prompt and must be followed exactly\n- **GitHub issue linking**: If a task has a linked issue, the agent receives the issue reference for branch naming and PR linking\n- **Cost-conscious sub-agents**: Agents are instructed to use cheap models for routine sub-agent work\n\n### Context Enrichment\n\nEvery agent receives a rich context built from multiple sources:\n\n| Context | Source | When | Description |\n|---|---|---|---|\n| **System prompt** | `prompts/system.md` | Always | Output format, JSON schema, workflow requirements, constraints |\n| **Task details** | GitHub Issue | Always | Title, body, labels, agent profile (role/skills/tools/constraints) |\n| **Error history** | Issue comments | On retries | Last 5 status transitions with timestamps (agent sees what already failed) |\n| **Last error** | Sidecar JSON | On retries | Most recent error message |\n| **GitHub issue comments** | GitHub API | If issue linked | Last 10 comments on the linked issue (agent sees discussion) |\n| **Prior run context** | `contexts/task-{id}.md` | On retries | Logs from previous attempts + tool call summaries |\n| **Repo tree** | `git ls-files` / `find` | Always | Truncated file listing (up to 200 files) |\n| **Project instructions** | `CLAUDE.md` + `AGENTS.md` + `README.md` | If files exist | Project-specific instructions and documentation |\n| **Skills docs** | `skills/{id}/SKILL.md` | If skills selected | Full skill documentation for each selected skill |\n| **Parent context** | Parent issue | For child tasks | Parent task summary + sibling task statuses |\n| **Git diff** | `git diff --stat HEAD` | On retries (attempts \u003e 0) | Current uncommitted changes |\n| **Output file path** | `.orchestrator/output-{id}.json` | Always | Where the agent writes its JSON results |\n\n### How Context Flows\n\n```\nrun_task.sh\n├── load_task()                    → TASK_TITLE, TASK_BODY, TASK_LABELS, AGENT_PROFILE_JSON, ...\n├── fetch_issue_comments()         → ISSUE_COMMENTS   (last 10 GitHub issue comments)\n├── task history + last_error      → TASK_HISTORY, TASK_LAST_ERROR\n├── load_task_context()            → TASK_CONTEXT     (prior run logs + tool summaries)\n├── build_parent_context()         → PARENT_CONTEXT   (parent summary + sibling statuses)\n├── build_project_instructions()   → PROJECT_INSTRUCTIONS  (CLAUDE.md + AGENTS.md + README.md)\n├── build_skills_docs()            → SKILLS_DOCS      (SKILL.md for each selected skill)\n├── build_repo_tree()              → REPO_TREE        (truncated file listing)\n├── build_git_diff()               → GIT_DIFF         (on retries only)\n│\n├── render_template(\"prompts/system.md\")  → SYSTEM_PROMPT  (workflow + output format)\n├── render_template(\"prompts/agent.md\")   → AGENT_MESSAGE  (all context above)\n│\n├── agent invocation (cd $PROJECT_DIR)\n│   ├── claude -p --permission-mode acceptEdits --output-format json ...\n│   ├── codex -q --json ...\n│   └── opencode run --format json ...\n│\n└── post-agent\n    ├── extract tool history         → tools-{id}.json\n    ├── extract token usage          → input_tokens, output_tokens\n    ├── capture stderr snippet       → stderr_snippet (500 chars)\n    ├── calculate duration           → duration (seconds)\n    ├── push branch if not main      → git push -u origin \u003cbranch\u003e\n    └── store metadata in sidecar + issue labels\n```\n\n### Output\nThe 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.\n\n## Usage\n\n| Command | Description |\n| --- | --- |\n| `orchestrator init` | Initialize orchestrator for current project. |\n| `orchestrator dashboard` | Overview: tasks, projects, worktrees. |\n| `orchestrator chat` | Interactive chat with the orchestrator. |\n| `orchestrator start` | Start server (uses `brew services` if installed via brew). |\n| `orchestrator stop` | Stop server. |\n| `orchestrator log` | Tail orchestrator log. |\n| `orchestrator agents` | List installed agent CLIs. |\n| `orchestrator --version` | Show version. |\n\n### Task Commands\n\n| Command | Description |\n| --- | --- |\n| `orchestrator task status` | Show status counts and recent tasks. |\n| `orchestrator task status -g` | Show global status across all projects. |\n| `orchestrator task add \"Build router\" \"Add LLM router\" \"orchestration\"` | Add a task (title required, body/labels optional). |\n| `orchestrator task plan \"Implement auth\" \"Add login, signup, reset\" \"backend\"` | Add a task that will be decomposed into subtasks first. |\n| `orchestrator task list` | List tasks (id, status, agent, parent, title). |\n| `orchestrator task tree` | Show parent/child task tree. |\n| `orchestrator task route 1` | Route task `1`. If no ID, route next `new` task. |\n| `orchestrator task run 1` | Run task `1`. If no ID, run next runnable. |\n| `orchestrator task next` | Route + run the next task in one step. |\n| `orchestrator task poll` | Run all runnable tasks in parallel (default 4 workers). |\n| `orchestrator task retry 1` | Retry a blocked/done task (reset to new). |\n| `orchestrator task unblock 1` | Unblock a blocked task (reset to new). |\n| `orchestrator task unblock all` | Unblock all blocked tasks. |\n| `orchestrator task agent 1 claude` | Force a task to use a specific agent. |\n| `orchestrator task stream 1` | Stream live agent output. |\n| `orchestrator task watch` | Poll loop every 10s. |\n\n### Scheduled Jobs\n\n| Command | Description |\n| --- | --- |\n| `orchestrator job add \"0 9 * * *\" \"Daily Sync\" \"Pull and check\" \"sync\"` | Add a scheduled task job. |\n| `orchestrator job add --type bash --command \"echo hi\" \"@hourly\" \"Ping\"` | Add a bash job (no LLM). |\n| `orchestrator job list` | List all jobs with status and next run. |\n| `orchestrator job remove daily-sync` | Remove a job. |\n| `orchestrator job enable daily-sync` | Enable a job. |\n| `orchestrator job disable daily-sync` | Disable a job. |\n\n### Skills\n\n| Command | Description |\n| --- | --- |\n| `orchestrator skills list` | List skills in the catalog. |\n| `orchestrator skills sync` | Sync skills from registry to `skills/`. |\n\n## Per-Project Isolation\n\nEach project is initialized separately and registered in a global project registry:\n\n```bash\ncd ~/projects/app-a \u0026\u0026 orchestrator init    # registers project\ncd ~/projects/app-b \u0026\u0026 orchestrator init    # registers project\n```\n\n`orchestrator init` does three things:\n1. Creates `orchestrator.yml` in the project root (GitHub repo, project ID)\n2. Creates `.orchestrator/` state directory\n3. Registers the project in `~/.orchestrator/projects.yml`\n\nThe registry file (`~/.orchestrator/projects.yml`) tracks all managed projects:\n```yaml\nprojects:\n  - name: app-a\n    path: /Users/you/projects/app-a\n  - name: app-b\n    path: /Users/you/projects/app-b\n```\n\nA single `orchestrator serve` polls all registered projects. Each project gets its own GitHub repo context, labels, and task isolation:\n```bash\ncd ~/projects/app-a \u0026\u0026 orchestrator task list  # shows only app-a tasks\ncd ~/projects/app-b \u0026\u0026 orchestrator task list  # shows only app-b tasks\n```\n\nUse `orchestrator dashboard` to see active projects and worktrees.\n\n## Background Service\n\n```bash\norchestrator start      # start (delegates to brew services)\norchestrator stop       # stop\norchestrator restart    # restart\norchestrator info       # check status\n```\n\nAuto-starts on login, auto-restarts on crash via `brew services`.\n\n## Scheduled Jobs (Cron)\n\nJobs 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).\n\n### How It Works\n1. Define a job with a cron expression and a task template.\n2. On each tick, the scheduler checks which jobs are due.\n3. Each job tracks its `active_task_id`. If that task is still in-flight (any status except `done`), the job waits — no duplicates.\n4. When the previous task completes, the job is free to create a new one on the next matching schedule.\n\n### Running the Scheduler\nThe server runs `job tick` on every poll cycle automatically:\n```bash\norchestrator start\n```\n\n### Job Definition\n\nTwo job types:\n- **task** (default): creates a task that goes through routing and agent execution\n- **bash**: runs a shell command directly, no LLM involved\n\n```yaml\n# .orchestrator/jobs.yml (per-project)\njobs:\n  - id: daily-sync\n    schedule: \"0 9 * * *\"\n    type: task              # default, creates agent task\n    task:\n      title: \"Daily code sync\"\n      body: \"Pull latest changes, run linting, check for issues\"\n      labels: [sync]\n      agent: \"\"             # empty = let router decide\n    enabled: true\n    last_run: null\n    last_task_status: null\n    active_task_id: null\n\n  - id: hourly-ping\n    schedule: \"@hourly\"\n    type: bash              # runs command directly\n    command: \"curl -s https://example.com/health\"\n    enabled: true\n```\n\n### Schedule Expressions\nStandard 5-field cron: `minute hour day_of_month month day_of_week`\n\nAliases: `@hourly`, `@daily`, `@weekly`, `@monthly`, `@yearly`\n\nSupports: wildcards (`*`), ranges (`1-5`), steps (`*/15`, `1-5/2`), lists (`1,3,5`).\n\n### Dedup \u0026 Safety\n- Each job has `active_task_id` tracking its current in-flight task.\n- If the task is `new`, `routed`, `in_progress`, or `blocked` — the job waits.\n- Tasks can delegate children, get reviewed, or get blocked — the job won't interfere.\n- Only when the task reaches `done` does the job create a new one.\n- Tasks from jobs get `scheduled` and `job:{id}` labels for easy filtering.\n- All job-created tasks sync to GitHub issues like any other task.\n\n## Dynamic Agent Profiles\nThe router generates a profile for each task, stored in the sidecar file. You can override routing via issue labels.\n\nExample:\n```yaml\nagent_profile:\n  role: backend specialist\n  skills: [api, sql, testing]\n  tools: [git, rg]\n  constraints: [\"no migrations\"]\n```\n\n## Skills Catalog\n`skills.yml` defines approved skill repositories and a catalog of skills. The router selects skill ids and stores them in `selected_skills`.\n\n### Required Skills\nSkills 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`:\n```yaml\nworkflow:\n  required_skills:\n    - github              # GitHub CLI operations\n    - gh-pr-polish        # PR titles and bodies\n```\n\n### Commit Pinning\nSkill repositories are pinned to audited commit SHAs in `skills.yml` to prevent supply chain attacks:\n```yaml\nrepositories:\n  - name: gabrielkoerich\n    url: https://github.com/gabrielkoerich/skills\n    pin: 226f5f11346eddceebf017746aa5cd660ef3af20\n```\nWhen pinned, `skills-sync` checks out the exact commit instead of pulling latest.\n\n### Syncing\nClone or update skills with:\n```bash\norchestrator skills sync\n```\n\n## Error Handling \u0026 GitHub Issue Feedback\n\nWhen an agent fails, the orchestrator classifies the error and blocks the task:\n\n### Error Classification\n| Error Type | Detection | Action |\n|---|---|---|\n| **Auth/billing** | Pattern match on stderr/stdout (401, 403, expired key, quota, etc.) | Block + comment on issue |\n| **Timeout** | Exit code 124 | Block + comment on issue |\n| **Generic failure** | Any non-zero exit | Block + comment on issue |\n| **Invalid response** | No JSON in output file or stdout | Block + comment on issue |\n\n### Retry Loop Detection\nIf the same error repeats 3 times (4+ attempts), the orchestrator detects a retry loop and blocks the task permanently with a clear message.\n\n### What Happens on Failure\n1. Task status set to `blocked` (no auto-retry)\n2. Error details saved to sidecar file and posted as issue comment\n3. Error logged to task history with timestamp\n4. **GitHub issue comment** posted with full details (see below)\n5. **Red `blocked` label** added to the GitHub issue (auto-created if missing)\n\n### Unblocking\nTasks stay blocked until you manually investigate and unblock them:\n1. Check the error on the GitHub issue\n2. Fix the underlying problem (e.g. add API key, fix code)\n3. Remove the `blocked` label from the issue\n4. Set the task status back to `new` — the orchestrator picks it up again\n\n### Agent-Reported Blocks\nAgents can also report blocks in their JSON response:\n- `status: blocked` — waiting for a dependency or missing information\n- The agent must provide a `reason` field explaining what happened, what it tried, and what it needs\n\nThe reason is posted as an issue comment, logged to the sidecar, and appended to `contexts/task-{id}.md`.\n\n## Logging \u0026 Observability\n\nAll state lives under `~/.orchestrator` (the install dir). Logs and runtime files are in the `.orchestrator/` subdirectory within it.\n\n### Log Files\n\n| File | Path | Description |\n|---|---|---|\n| **Server log** | `.orchestrator/orchestrator.log` | Main loop output: ticks, poll, gh sync, restarts |\n| **Archive log** | `.orchestrator/orchestrator.archive.log` | Previous server sessions (rotated on each start) |\n| **Jobs log** | `.orchestrator/jobs.log` | Scheduled job execution: triggers, task creation, bash output |\n| **Brew log** | `/opt/homebrew/var/log/orchestrator.log` | stdout when running via `brew services` |\n\nView the server log:\n```bash\norchestrator log          # tail last 50 lines\norchestrator log 200      # tail last 200 lines\n```\n\nOr tail it live while the server runs:\n```bash\nTAIL_LOG=1 orchestrator serve\n```\n\n### Per-Task Logs\n\n| File | Path | Description |\n|---|---|---|\n| **Task context** | `contexts/task-{id}.md` | Appended after each run: timestamp, status, summary, reason, files, tool summary |\n| **Task history** | Issue comments | Status transitions with timestamps and notes |\n| **Agent output** | `.orchestrator/output-{id}.json` | Structured JSON from the last agent run |\n| **Agent prompt** | `.orchestrator/prompt-{id}.txt` | Full system prompt + agent message (with SHA-256 hash) |\n| **Agent response** | `.orchestrator/response-{id}.txt` | Raw stdout from the agent |\n| **Agent stderr** | `.orchestrator/stderr-{id}.txt` | Stderr captured from the agent (auth errors, warnings) |\n| **Tool history** | `.orchestrator/tools-{id}.json` | Every tool call the agent made (Bash, Edit, Read, etc.) with error flags |\n| **Route prompt** | `.orchestrator/route-prompt-{id}.txt` | The prompt sent to the router (for debugging routing decisions) |\n| **Failed response** | `contexts/response-{id}.md` | Raw agent output when JSON parsing fails |\n\n### GitHub Issue Comments\n\nWhen GitHub sync is enabled, each status update posts a structured comment on the linked issue:\n\n```\n## 🟣 Claude Fixed the authentication bug     ← agent badge + summary as title\n\n| | |\n|---|---|\n| **Status** | `done` |                       ← metadata table\n| **Agent** | claude |\n| **Model** | `claude-sonnet-4-5-20250929` |\n| **Attempt** | 2 |\n| **Duration** | 3m 42s |\n| **Tokens** | 15k in / 3k out |\n| **Prompt** | `a1b2c3d4` |\n\n### Errors \u0026 Blockers                          ← only when blocked\n**Reason:** SSH key not configured\n\u003e `git push: Permission denied (publickey)`\n- Need SSH key configured for git push\n\n### Accomplished                               ← bullet list\n- Fixed memcmp offset from 40 to 48\n- Added test coverage for edge case\n\n### Remaining\n- Deploy to staging\n\n### Files Changed\n- `src/auth.ts`\n- `tests/auth.test.ts`\n\n### Agent Activity                             ← tool call summary\n| Tool | Calls |\n|------|-------|\n| Bash | 12 |\n| Edit | 5 |\n| Read | 8 |\n\n\u003cdetails\u003e\u003csummary\u003eAgent stderr\u003c/summary\u003e       ← collapsed\n...\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003ePrompt sent to agent\u003c/summary\u003e ← collapsed\n...\n\u003c/details\u003e\n```\n\nFeatures:\n- **Agent badges**: 🟣 Claude, 🟢 Codex, 🔵 OpenCode\n- **Content-hash dedup**: identical comments are not re-posted (prevents spam)\n- **Atomic sync**: `gh_synced_at` copies `updated_at` at write time (no stale variable bugs)\n- **Blocked label**: red `blocked` label auto-applied/removed based on status\n- **Owner ping**: `@owner` tagged on blocked tasks\n\n### What Gets Logged Where\n\n| Event | Server log | Task context | Task history | GitHub comment | Per-task files |\n|---|---|---|---|---|---|\n| Tick/poll cycle | x | | | | |\n| Task started | x | | x | | prompt saved |\n| Agent completed | x (duration, tokens, tools) | x (tool summary) | x | x (full report) | response + stderr + tools |\n| Agent blocked/stuck | x | x | x | x (comment + label) | response + stderr + tools |\n| Auth/billing error | x | | x | x (comment + label) | stderr |\n| Timeout | x | | x | x (comment + label) | |\n| Invalid response | x | x (raw saved) | x | x (comment + label) | response |\n| Retry loop (3x same error) | x | | x | x (comment + label) | |\n| Review result | x | | x | x | |\n| Delegation | x | | x | x | |\n| Job triggered | x | | | | |\n| Config/code restart | x | | | | |\n\n## Per-Project Config\n\nPlace 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`.\n\n```yaml\n# myproject/.orchestrator.yml\nrequired_tools: [\"bun\"]\ngh:\n  repo: \"myorg/myproject\"\n  project_id: \"PVT_...\"\n  sync_label: \"\"\nworkflow:\n  auto_close: false\n  review_owner: \"@myhandle\"\nrouter:\n  model: \"sonnet\"\n```\n\nThis lets you:\n- Use a different GitHub repo/project per project\n- Customize workflow settings (review, auto-close) per project\n- Override the router model or fallback agent\n- Keep project-specific config in version control\n\nThe server restarts automatically when `.orchestrator.yml` changes.\n\n## Config Reference\nAll runtime configuration lives in `config.yml`.\n\n| Section | Key | Description | Default |\n| --- | --- | --- | --- |\n| top-level | `project_dir` | Override project directory (auto-detected from CWD). | `\"\"` |\n| top-level | `required_tools` | Tools that must exist on PATH before launching an agent. | `[]` |\n| `workflow` | `auto_close` | Auto-close GitHub issues when tasks are `done`. | `true` |\n| `workflow` | `review_owner` | GitHub handle to tag when review is needed. | `@owner` |\n| `workflow` | `enable_review_agent` | Run a review agent after completion. | `false` |\n| `workflow` | `review_agent` | Fallback reviewer when opposite agent unavailable. | `claude` |\n| `workflow` | `max_attempts` | Max attempts before marking task as blocked. | `10` |\n| `workflow` | `timeout_seconds` | Task execution timeout (0 disables timeout). | `1800` |\n| `workflow` | `timeout_by_complexity` | Per-complexity task timeouts (takes precedence). | `{}` |\n| `workflow` | `required_skills` | Skills always injected into agent prompts (marked `[REQUIRED]`). | `[]` |\n| `workflow` | `disallowed_tools` | Tool patterns blocked via `--disallowedTools`. | `[\"Bash(rm *)\",\"Bash(rm -*)\"]` |\n| `router` | `agent` | Default router executor. | `claude` |\n| `router` | `model` | Router model name. | `haiku` |\n| `router` | `timeout_seconds` | Router timeout (0 disables timeout). | `120` |\n| `router` | `disabled_agents` | Agents to exclude from routing (e.g. `[opencode]`). | `[]` |\n| `router` | `fallback_executor` | Fallback executor when router fails. | `codex` |\n| `router` | `allowed_tools` | Default tool allowlist used in routing prompts. | `[yq, jq, bash, ...]` |\n| `router` | `default_skills` | Skills always included in routing. | `[gh, git-worktree]` |\n| `llm` | `input_format` | CLI input format override. | `\"\"` |\n| `llm` | `output_format` | CLI output format override. | `\"json\"` |\n| `gh` | `enabled` | Enable GitHub sync. | `true` |\n| `gh` | `repo` | Default repo (`owner/repo`). | `\"owner/repo\"` |\n| `gh` | `sync_label` | Only sync tasks/issues with this label (empty = all). | `\"sync\"` |\n| `gh` | `project_id` | GitHub Project v2 ID. | `\"\"` |\n| `gh` | `project_status_field_id` | Status field ID in Project v2. | `\"\"` |\n| `gh` | `project_status_names` | Mapping for `backlog/in_progress/review/done` status option names (used to resolve option IDs). | `{}` |\n| `gh` | `project_status_map` | Mapping for `backlog/in_progress/review/done` option IDs. | `{}` |\n| `gh.backoff` | `mode` | Rate-limit behavior: `wait` or `skip`. | `\"wait\"` |\n| `gh.backoff` | `base_seconds` | Initial backoff duration in seconds. | `30` |\n| `gh.backoff` | `max_seconds` | Max backoff duration in seconds. | `900` |\n\n## Context Persistence\nTask and profile contexts are persisted under `contexts/`:\n- `contexts/task-\u003cid\u003e.md` — logs from each run (status, summary, reason, files)\n- `contexts/profile-\u003crole\u003e.md` — role-specific context\n\nThe orchestrator loads both into the prompt and appends a log entry after each run.\n\n## Task Decomposition (Plan Mode)\n\nComplex tasks can be broken down into smaller subtasks before execution. This happens in two ways:\n\n### Automatic (router decides)\nThe 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.\n\n### Manual (user decides)\nAdd the `plan` label when creating a task:\n```bash\norchestrator task plan \"Implement user auth\" \"Add login, signup, password reset with JWT tokens\" \"backend\"\n```\n\nOr add a task with the `plan` label directly:\n```bash\norchestrator task add \"Redesign the API\" \"...\" \"plan,backend\"\n```\n\n### How It Works\n1. The agent receives `prompts/plan.md` instead of the execution prompt\n2. It reads the codebase, analyzes the task, and returns only delegations (no code changes)\n3. Each subtask gets a clear title, detailed body with acceptance criteria, labels for routing, and a suggested agent\n4. The parent blocks until all children complete, then resumes with the execution prompt\n\n### Guidelines in the planning prompt\n- Each subtask should be completable in a single agent run\n- Subtasks are listed in dependency order\n- Bodies include specific file paths, function names, and expected behavior\n- Prefers 3-7 subtasks (not too granular, not too broad)\n- Includes a testing/verification subtask at the end\n\n## Delegation\nIf the agent returns this:\n```json\n{\n  \"needs_help\": true,\n  \"delegations\": [\n    {\n      \"title\": \"Add unit tests\",\n      \"body\": \"Test routing logic\",\n      \"labels\": [\"tests\"],\n      \"suggested_agent\": \"codex\"\n    }\n  ]\n}\n```\nThe orchestrator will:\n- Create child tasks\n- Block the parent until children are done\n- Re-run the parent via `poll` or `rejoin`\n\n## Concurrency + Locking\n- `poll` runs new tasks in parallel.\n- SQLite WAL mode handles concurrent reads; per-task locks prevent double-run.\n- Each task also has a per-task lock to prevent double-run.\n- Stale locks are auto-cleared after `LOCK_STALE_SECONDS` (default 600).\n\n## Review Agent (Optional)\n\nAutomatically review PRs using a different agent from the one that wrote the code.\n\n```yaml\nworkflow:\n  enable_review_agent: true\n```\n\nWhen enabled, after an agent completes a task and a PR is open:\n\n1. **Opposite agent selected** — if codex wrote the code, claude reviews (and vice versa). Falls back to `workflow.review_agent` config if only one agent is available.\n2. **PR diff fetched** — the actual PR diff via `gh pr diff` (first 500 lines).\n3. **Real GitHub review posted** — via `gh pr review`:\n   - `approve` → green checkmark review on the PR\n   - `request_changes` → red X review, task goes to `needs_review`\n   - `reject` → review + PR closed, task goes to `needs_review`\n\nThe `review_agent` config key is now an optional fallback — `opposite_agent()` handles primary selection.\n\nOverride the reviewer for a specific run:\n```bash\nREVIEW_AGENT=claude orchestrator task run \u003cid\u003e\n```\n\nSee [Review Agent docs](docs/content/review-agent.md) for full details.\n\n## GitHub Setup\nGitHub Issues is the native task backend. Authentication is handled by `gh`.\n\n\u003cdetails\u003e\n\u003csummary\u003eGitHub Integration: Token Type and Permissions\u003c/summary\u003e\n\n**Recommended:** Fine-grained PAT scoped to the target repo(s).\n\nRepository permissions:\n- Issues: Read + Write\n- Metadata: Read\n- Contents: Read (optional)\n\nOrganization permissions (for Projects v2):\n- Projects: Read + Write\n\nClassic PATs will also work but are broader in scope.\n\u003c/details\u003e\n\n### GitHub Setup\n1. Install and authenticate:\n```bash\ngh auth login\n```\n2. Verify access:\n```bash\ngh repo view\n```\nProject fields belong in `config.yml`:\n```yaml\ngh:\n  project_id: \"\"\n  project_status_field_id: \"\"\n  project_status_map:\n    backlog: \"\"\n    in_progress: \"\"\n    review: \"\"\n    done: \"\"\n```\nTo discover Project field and option IDs:\n```bash\norchestrator project info\n```\nTo auto-fill the Status field/options into config:\n```bash\norchestrator project info --fix\n```\n\n### Manual sync commands\n```bash\norchestrator gh pull    # import new issues as tasks\norchestrator gh push    # push local status updates to GitHub\norchestrator gh sync    # both directions\n```\nNote: The background service runs `gh sync` automatically every 120s.\n\n### Error Comments \u0026 Blocking\nWhen a task fails (any error), the orchestrator:\n1. Posts a structured comment on the linked GitHub issue (see \"GitHub Issue Comments\" above)\n2. Adds a red `blocked` label to the issue (auto-created if missing)\n3. Sets the task status to `blocked`\n4. Tags `@owner` for attention\n\nComments 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.\n\n### Notes\n- The repo is resolved from `config.yml` or `gh repo view`.\n- Issues are created for tasks without `gh_issue_number`.\n- If a task has label `no_gh` or `local-only`, it will not be synced.\n- If `config.yml` `gh.sync_label` is set, only tasks/issues with that label are synced.\n- If `config.yml` `gh.enabled` is `false`, GitHub sync is disabled.\n- Task status is synced to issue labels using `status:\u003cstatus\u003e`.\n- When a task is `done`, `auto_close` controls whether to close the issue or tag the owner for review.\n- Scheduled job tasks get `scheduled` and `job:{id}` labels and sync to GitHub like any other task.\n- Agents never call GitHub directly; the orchestrator posts comments and status updates so it can back off safely when rate-limited.\n\n### GitHub Backoff\nWhen GitHub rate limits or abuse detection triggers, the orchestrator sleeps and retries instead of hammering the API.\n\nConfig keys:\n- `gh.backoff.mode` — `wait` (default) or `skip`\n- `gh.backoff.base_seconds` — initial backoff duration\n- `gh.backoff.max_seconds` — maximum backoff duration\n\nThe backoff is shared across pull/push/comment/project updates, so a single rate limit event pauses all GitHub writes.\n\n### Projects (Optional)\nProvide in `config.yml`:\n- `gh.project_id`\n- `gh.project_status_field_id`\n- `gh.project_status_map` (Backlog/In Progress/Review/Done option IDs)\n\n#### Finding IDs\n1. Project ID (GraphQL):\n```bash\ngh api graphql -f query='query($org:String!, $num:Int!){ organization(login:$org){ projectV2(number:$num){ id } } }' -f org=YOUR_ORG -f num=PROJECT_NUMBER\n```\n2. Status field ID + option IDs (or use `orchestrator project info`):\n```bash\ngh 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\n```\n\n## Notes\n- GitHub Issues are the source of truth for tasks. Labels drive status.\n- Routing and profiles are LLM-generated; you can override them via issue labels.\n- Agents run in agentic mode inside isolated git worktrees with full tool access.\n- The router stays non-agentic (`--print`) — it's a classification task.\n\n---\n\n## Development\n\n```bash\ngit clone https://github.com/gabrielkoerich/orchestrator.git\ncd orchestrator\nbats tests          # run tests\njust                # list available commands\n```\n\nRequires: `yq`, `jq`, `just`, `python3`, `rg`, `fd`, `bats`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabrielkoerich%2Forchestrator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgabrielkoerich%2Forchestrator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabrielkoerich%2Forchestrator/lists"}