Skip to content

Instantly share code, notes, and snippets.

@BlinkyStitt
Created June 21, 2026 19:50
Show Gist options
  • Select an option

  • Save BlinkyStitt/52c89ec6f91a957449458d673b66c0e5 to your computer and use it in GitHub Desktop.

Select an option

Save BlinkyStitt/52c89ec6f91a957449458d673b66c0e5 to your computer and use it in GitHub Desktop.
Droid-orchestrator readme June 21

droid-orchestrator

droid-orchestrator is a Rust binary that schedules harness-backed workers against Linear issues and enforces approval, dependency, GitHub PR merge-gate, Linear-label-backed repo locks, sandboxing, and test-validation rules. Harness selection is automatic from the routed model: OpenAI models use Codex when configured, Anthropic models use Claude when configured, and other model families use Droid when configured. If no routed harness is available, work waits instead of requiring a fallback. It runs as the default scheduler, a loopback API, or a feature-gated real smoke validation runner, and exposes OpenAPI metadata that can be bridged to MCP tooling.

I found having to bounce between 7 different tabs was a really bad experience.

The goal is to be able to write more good code faster. For the purposes of this experiment, I've accepted that means I'm not going to type it myself. I'm going to let the AI write it, and test it, and deploy it. And then I'll use it and probably find some things wrong. But then I just make some more issues. Frankly, even if you spend a ton of time trying to polish the perfect spec on the first try, you're going to miss something and end up needing more issues to fix bugs and problems anyways. So lets really try to exercise "Make it work, make it right, make it fast". Don't try to make the software "right" first. Just get something moving. Then you can keep adding to it and adjusting it the work it does is actually right. If you've architected the code well, it will be fast and you'll be done. If you haven't architected the code well, it will be slow and you'll have to probably rewrite large sections to refactor things.

I don't want to spend time reading all those PRs. That sounds awful. I want to throw ideas at a project board and come back to it later to see questions and research done for me. Then I'll adjust those ideas to match what I really want and let the AI try to solve it. If when the task is closed they did a bad job, I can look into improving this pipeline. But the pipeline already has a lot of steps and I think it's mostly ready to use on real applications now. I'm building droid-orchestrator itself with droid-orchestrator.

There's still a lot to think about in this space. But I just want to get some apps in a way that isn't painful.

Unfortunately, this thing can spit out reviews so fast that it feels a bit like work keeping up with it. I am designing "droid-conductor" as a way to turn a boring review meeting into something fun.

Task Pipelines

The scheduler uses Linear labels and comments to direct a team of agents through structured tasks.

When you make a task, set its Linear project, difficulty, and priority. Give a descriptive title and as short or long of a desciption as you'd like. The orchestrator will automatically begin work only after the issue has a Linear project; project-less issues wait for human project assignment instead of being guessed into a project. When there is work for you to comment on, the task will be reassigned to you. You should have the issue closed in no time!

Assign the task back to "unassigned" and the orchestrator will resume work.

Planning and admission

flowchart TD
    Candidate[Linear candidate issue] --> Gates[Project assignment, project allowlist, assignment, approval, dependency, active-run, and capacity gates]
    Gates --> Priority{Linear priority set?}
    Priority -- Missing --> PriorityRole[Priority classifier]
    PriorityRole --> NextPass[Return to scheduler candidate pool]
    Priority -- Present --> RepoGates[GitHub PR and repo-lock gates for repo-writing work]
    RepoGates --> State{Derived issue state}

    State -- Open intake/spec questions --> HumanQuestions[Assign Bryan in In Review]
    State -- Ready approved spec --> ReadyImplementer[Implementer with approved spec]
    State -- Investigation findings recorded --> InvestigationDone[Source issue has findings]

    State -- Needs spec --> Intake[Intake question check]
    Intake -- investigation needed label --> Investigation[Investigation role]
    Investigation --> Findings[Post investigation findings on source issue]
    Findings --> RelatedTasks[Create linked non-child follow-up tasks when warranted]
    RelatedTasks --> InvestigationDone
    Intake -- Questions found --> HumanQuestions
    Intake -- No questions --> Difficulty[Difficulty label or difficulty-check role]
    Difficulty -- Trivial --> DirectSpec[Synthesize direct implementation spec]
    DirectSpec --> TrivialImplementer[Implementer without spec-writer]

    Difficulty -- Easy/Medium/Hard --> DocsResearch[Docs research if missing]
    DocsResearch --> PlanningRoute{Planning route}
    PlanningRoute -- Easy/Medium: spec --> MediumSpec[Spec-writer]
    PlanningRoute -- Hard: code research if missing --> CodeResearch[Code research]
    CodeResearch --> HardSpec[Spec-writer]
    MediumSpec --> Approval[Assign Bryan in In Review for approval]
    HardSpec --> Approval
    Approval --> ReadyImplementer

    ReadyImplementer --> Implementation[Implementation handoff pipeline]
    TrivialImplementer --> Implementation
Loading

Direct implementation is reserved for trivial work only: low risk, no ambiguity, and scriptable or directly observable acceptance criteria. This is the only current implementation lane that may skip a human-approved spec. easy work uses the normal spec and approval path but with lower reasoning levels and cheaper models.

Every automatically worked issue must have both a Linear project and at least one repo label. Missing repo labels make an issue ineligible for automated work and surface as "repo" label required in dry-run/status output. Missing Linear project is a separate admission blocker surfaced as project assignment required.

Issues labeled investigation needed use a separate investigation pipeline. They run the investigation role while no fresh findings exist, post an investigation findings artifact/comment on the source issue, and may create linked follow-up implementation issues through the orchestrator control API. Those follow-ups must include explicit priority, inherit the source project by default, keep requested labels and robot markers, include ai generated issue, and are linked as non-child, non-blocking related tasks. Alternate projects require explicit project ids; source issues without projects or ambiguous follow-up projects stop for clarification instead of creating project-less issues. Fresh findings stop the source investigation from looping back into spec writing or dispatching an implementer, and the source investigation closes only after all intended follow-up tasks are durably created and linked. A newer human comment after the findings makes the investigation stale and routes it back through investigation.

If acceptance criteria cannot be expressed as deterministic checks or directly observable outcomes, the issue returns to spec refinement instead of implementation.

Implementation handoff

flowchart TD
    Implementation[Admitted implementer work] --> HardCheck{Hard issue?}
    HardCheck -- Yes --> CodeResearch[Code research if missing]
    HardCheck -- No --> RepoPreflight[Materialize repo/worktree and run clean-git preflight]
    CodeResearch --> RepoPreflight
    RepoPreflight --> Interrupted{Interrupted implementer run?}
    Interrupted -- No and clean --> Worker[Implementer edits, targeted validation, commits, pushes PR]
    Interrupted -- No and dirty/error --> PreSpawnBlocker[Record pre-spawn blocker before worker spawn]
    Interrupted -- Yes --> Preserve[Commit dirty non-ignored work as interrupted work]
    Preserve --> Rebase[Fetch origin/main and rebase Linear branch]
    Rebase -- Clean apply with saved session --> ResumeWorker[Resume saved harness session]
    Rebase -- Clean apply without saved session --> Worker
    Rebase -- Conflict --> FreshRestart[Archive git evidence, reset branch/worktree to origin/main]
    FreshRestart --> Worker
    ResumeWorker --> Worker
    Worker --> Report[Report PR and call completeForReview through control API]
    Report --> TestValidation[orchestrator-step:test-validation]
    TestValidation --> RepoChecks{Commit on base, non-empty diff, clean worktree, PR reported?}

    PreSpawnBlocker --> Failure[Record validation failure and route remediation]
    RepoChecks -- No --> Failure[Record test-validation failure and route remediation]
    RepoChecks -- Yes --> ScriptedValidator[Scripted validator runs decomposed validation bundle]
    ScriptedValidator --> RequiredChecks{Repo validation and security scans pass?}
    RequiredChecks -- No --> Retry{First scripted-gate failure at current difficulty?}
    Retry -- Yes --> Remediation[Same-difficulty remediation retry]
    Remediation --> Worker
    Retry -- No --> Promote[Promote difficulty or assign human for repeated hard failure]
    RequiredChecks -- Yes --> Merge[Orchestrator merges PR]
    Merge --> PostMerge[Post-merge cleanup after merge]
    PostMerge --> RemainingRepos{Remaining repos?}
    RemainingRepos -- Yes --> NextRepo[Return issue to ready scheduling for next repo]
    RemainingRepos -- No --> Done[Linear closeout]
Loading

The test-validation step is assigned as soon as the implementer reports completion through completeForReview. It includes repository readiness checks, PR-report checks, sanitized command execution, and the configured validation bundle. The scripted validator is the only current merge authority. It runs the repo's canonical validation bundle, equivalent to just validate, decomposes independent steps and runs them concurrently where safe, includes required security scanners such as gitleaks, and fails the merge gate if any command fails. LLM roles may produce implementation work or context, but no LLM can approve, reject, or block a merge.

Implementers run targeted, cheap validation before opening or updating a PR to catch obvious failures. The authoritative full validation gate is orchestrator-owned and runs after completeForReview.

Implementer worker spawn happens only after repo preflight. Normal implementer launches materialize the repo/worktree, fetch the configured base (origin/main by default), ensure the Linear branch worktree is on the expected branch, has no unresolved conflicts, and has a clean non-ignored git status; dirty normal worktrees stop as a pre-spawn blocker instead of launching a worker. Interrupted implementer recovery is the exception: the scheduler keeps the repo lock held, commits dirty non-ignored work as interrupted work, fetches and rebases the Linear branch onto origin/main, then resumes the saved harness session when one exists. If that interrupted rebase conflicts, the scheduler archives git evidence, resets the Linear branch and worktree to origin/main, and starts a fresh implementer prompt from the approved spec.

Linear-backed pipeline state

Linear is the durable workflow database. The scheduler may keep local caches and run logs, but it must resume from Linear issue labels plus orchestrator comments.

  • orchestrator-step is a Linear label group. Exactly one child label is active on an issue at a time: priority-classification, intake, question-answering, difficulty-classification, docs-research, deep-docs-research, spec-writing, approval, code-research, implementation, test-validation, validation-remediation, github-merge, post-merge-cleanup, or complete. Legacy handoff-verification labels are read as test-validation and repaired to the current label. Legacy human-intervention labels may be read from older issues, but new human handoffs keep the next desired droid step.
  • difficulty remains the exclusive difficulty label group: trivial, easy, medium, or hard. Scripted validation failures retry once at the same difficulty; a repeated scripted-gate failure promotes to the next difficulty, and a repeated hard failure assigns Bryan with an Expected action: comment while leaving the step at validation-remediation.
  • orchestrator-fact-* labels are flat labels, not a group. They accumulate durable facts such as orchestrator-fact-intake-complete, orchestrator-fact-docs-researched, orchestrator-fact-code-researched, orchestrator-fact-pr-reported, orchestrator-fact-test-validation-succeeded, orchestrator-fact-test-validation-failed, and orchestrator-fact-github-merged. Legacy orchestrator-fact-pre-merge-validation-succeeded and orchestrator-fact-handoff-failed labels remain readable for older issues but are not written for new validation outcomes.
  • orchestrator-lock is a Linear label group for the currently active repo write lock. Child labels encode repo slugs by replacing / with --, for example SatoshiAndKin--droid-orchestrator.
  • Internal grouped-label keys use group:child notation when they appear in logs, tests, or code. Flat fact labels use hyphens only and never : or /.
  • Assign Bryan only when the active step requires human work, such as question-answering, approval, or an explicit orchestrator handoff comment containing Expected action:. Human assignment is the handoff signal; do not replace the desired droid step with a generic human-intervention step. Agent-owned steps proceed only when the issue is already unassigned; the only clearing path is invalid human assignment repair, which removes an assignment the orchestrator should not have applied while active agent-owned work is already in progress.
  • Issues without a Linear project are not admitted to agent work. Assign the project first; the scheduler must not infer or auto-assign a default project.

Current pipeline labels:

Order Active step label Owner Exit condition
1 orchestrator-step:priority-classification Priority classifier Linear priority is set.
2 orchestrator-step:intake Intake Missing questions are posted for Bryan or the issue is ready for difficulty/spec routing.
3 orchestrator-step:question-answering Bryan Bryan answers intake/spec questions; the orchestrator refreshes the spec path.
4 orchestrator-step:difficulty-classification Difficulty checker Difficulty is set to trivial, easy, medium, or hard.
5 orchestrator-step:docs-research Docs research Required documentation context is posted or marked unnecessary.
6 orchestrator-step:deep-docs-research Deep docs research Internet-backed documentation research is posted or skipped by policy.
7 orchestrator-step:spec-writing Spec writer A complete spec is posted for approval, or a direct trivial spec is synthesized.
8 orchestrator-step:approval Bryan Bryan approves the current complete spec.
9 orchestrator-step:code-research Code research Hard-work codebase findings are posted before implementation.
10 orchestrator-step:implementation Implementer Branch is pushed, PR is reported, and completeForReview is called.
11 orchestrator-step:test-validation Orchestrator validator Repository readiness checks and the configured validation bundle pass, or failure routes to remediation.
12 orchestrator-step:validation-remediation Implementer or Bryan Validation failure is fixed, retried, promoted, or escalated with Expected action:.
13 orchestrator-step:github-merge Orchestrator merge gate GitHub PR merges or remains blocked by GitHub status/mergeability.
14 orchestrator-step:post-merge-cleanup Orchestrator cleanup Worktree, Compose state, repo lock, and repo completion ledger are cleaned up.
15 orchestrator-step:complete Orchestrator closeout All required repos are complete and the Linear issue is closed.

Multi-repo issues should split into per-repo child or follow-up issues by default. Keep one multi-repo issue only when the change is truly atomic across repositories. Repo labels are required for automated work and serialize shared repo writes; read-only work outside the locked repo can parallelize, but a repo lock stays held through PR merge and post-merge cleanup so same-repo work waits until the lock clears.

Requirements

  • Rust toolchain with edition 2024 support.
  • cargo and just.
  • Optional codex, claude, and droid CLIs configured in [paths] for the harnesses this deployment can run.
  • ski-factory's core plugin/hooks installed and configured for every supported harness.
  • A Linear API key for live Linear operations.

Configuration

droid-orchestrator is configured via a TOML file. By default, it loads ~/.droid-orchestrator/config.toml. Use --config <path> when you need to run with a different config file.

Copy droid-orchestrator.example.toml and edit the values as needed:

mkdir -p ~/.droid-orchestrator
cp droid-orchestrator.example.toml ~/.droid-orchestrator/config.toml

Example [linear] configuration block:

[linear]
default_github_owner = "SatoshiAndKin"
# ... other linear fields

Example harness path configuration:

[paths]
# Optional. Configure only the harnesses available on this machine.
codex_bin = "codex"
claude_bin = "claude"
droid_bin = "droid"

Sensitive values and smoke fixture selectors are set via environment variables. Copy env.example and set local values before running live workflows:

cp env.example .env

Key environment variables:

  • FACTORY_API_KEY: Factory API token. Keep this secret.
  • DROID_ORCHESTRATOR_CAPABILITY_TOKEN: Optional fixed token for interactive API workflows.
  • DROID_ORCHESTRATOR_SMOKE_LINEAR_*: Linear IDs and settings for feature-gated real smoke validation.

Regular settings live in config.toml:

  • [linear].api_url: Linear GraphQL endpoint. Defaults to https://api.linear.app/graphql.
  • [linear].projects: Optional allowlist of Linear project names to monitor. Regardless of this allowlist, each worked issue must already have a Linear project.
  • [linear.repo_aliases]: Optional map from stale Linear repo labels to canonical GitHub repo slugs before scheduling, locking, and PR scans.
  • [orchestrator].database_url: Optional MySQL store for restart-visible workflow evidence: task status, per-repo completion progress, role-run audit/cost records, approved specs/research, and PR handoff metadata. Omit it for the bundled Docker Compose MySQL; container runs use mysql:3306, and host runs resolve the current published compose port. Repo locks are the issue's Linear repo labels plus workflow state, not database rows, fixed-time lock expiry, or local replay records.
  • [orchestrator].database_max_connections: MySQL pool cap for that workflow-evidence store. Defaults to 64; raise it if scheduler concurrency grows beyond the default parallelism envelope.
  • [orchestrator].dry_run: Scheduler dry-run mode. You can also run one pass with --dry-run.
  • [orchestrator].roles: Optional role filter for current roles such as triage, priority, intake, difficulty-check, docs-research, code-research, spec-writer, implementer, and test-runner.
  • [parallelism]: Global workers ceiling, the global model total_weight budget, CPU-heavy cap, and explicit model weights for custom/BYOK routes.
  • [api].bind: API bind address. Defaults to 127.0.0.1:7373; api --bind <addr> is an explicit CLI override.
  • [api].allow_non_loopback_bind: Allows non-loopback API binding when set to true. Defaults to false.
  • [paths].droid_bin, [paths].gh_bin, [paths].just_bin, and [paths].justfile: Tool and Justfile paths.
  • [handoff].base_branch, [handoff].validate_cmd, and [handoff].validate_timeout_secs: Test-validation base ref, command, and timeout settings.

Repository clones are cached under your configured home directory (default ~/.droid-orchestrator/repos/), with logs under logs/.

The scheduler also reads TOML config from $DROID_ORCHESTRATOR_HOME/config.toml; see droid-orchestrator.example.toml. The global worker ceiling is configured as:

[parallelism]
workers = 8
total_weight = 120

workers defaults to detected CPU capacity times two when omitted. total_weight is a global harness-admission budget. Each launched model consumes max(1, ceil(input_usd_per_million + output_usd_per_million)) from known pricing data. Custom or BYOK model ids without known pricing must be listed under [parallelism.model_weights].

Pipeline

Normal implementation path: candidate issue -> priority if Linear priority is unset -> intake/spec planning -> difficulty checks and docs checks -> spec writer or trivial direct spec -> implementer -> completeForReview -> test-validation -> orchestrator merge -> post-merge cleanup -> Linear closeout.

Investigation path: candidate issue with investigation needed label -> investigation role -> findings on the source issue -> optional linked non-child follow-up tasks with explicit priority/project -> source investigation closeout after follow-up create/link succeeds. Implementers run on the follow-up implementation issues, not on the source investigation issue.

The priority role is metadata-only: it has no repo checkout, no worktree, and only sets Linear priority when the issue still has no priority after a fresh re-fetch.

Workstation setup for cross-repo agent runs

If you run agents across multiple repositories, configure MCP servers at the user level (~/.factory/mcp.json), not only in a single repository's .factory/mcp.json.

  1. Add global MCP servers:
droid mcp add linear https://mcp.linear.app/mcp --type http
droid mcp add sentry https://mcp.sentry.dev/mcp --type http
droid mcp add orchestrator-openapi "uvx awslabs.openapi-mcp-server" --type stdio \
  --env API_NAME=droid_orchestrator_control_api \
  --env API_BASE_URL=http://127.0.0.1:7777 \
  --env API_SPEC_URL=http://127.0.0.1:7777/openapi.json \
  --env LOG_LEVEL=info \
  --env ENABLE_PROMETHEUS=false \
  --env ENABLE_OPERATION_PROMPTS=true
  1. Authenticate Linear MCP and Sentry MCP (OAuth):

    • Start droid.
    • Run /mcp.
    • Select each server and complete Authenticate in the browser.
  2. Configure the orchestrator service's Linear API access:

export LINEAR_API_KEY=lin_api_xxx

Persist this in your shell profile (~/.zshrc, ~/.bashrc, etc.) for long-lived setups.

  1. Configure GitHub CLI for git/PR operations:
gh auth login -p https -w
gh auth setup-git
gh auth status
  1. Run the orchestrator API (required by orchestrator-openapi MCP tools):
cargo run --locked -- api --bind 127.0.0.1:7777
  1. Verify MCP tool visibility from any repository:
droid exec --list-tools

Common commands

# Show available just recipes
just --list

# First full local run: preflight checks, start Docker Compose services, then run the scheduler
just

# Run the scheduler directly
cargo run --locked

# Run a dry scheduler pass
just dry-run

# Run a dry scheduler pass with full skipped issue, prompt, and spec detail
just dry-run --verbose

# Run one live scheduler pass, then exit
just run-once

# Validate scheduler gates, secrets, formatting, clippy, and tests
just validate

# Run tests directly
cargo test

# Run nextest
cargo nextest run --all-targets --locked

Operating modes

Scheduler

Run the default scheduler:

cargo run --locked

Use just, just run, or just orchestrate for the full local scheduler run, and just dry-run for a concise strict dry scheduler pass. Dry-run always validates scheduler invariants and exits nonzero when candidate discovery is inconclusive, GitHub PR mergeability is hidden, a mapped conflicted PR has no selected repair work, same-repo work is planned while a local lock or non-exempt open PR blocks the repo, or implementation work is selected without repo context. Issues without repo labels are skipped as ineligible; read-only work outside the locked repo can still run while repo write locks or open PR gates exist. Use just dry-run --verbose to include full skipped issue, prompt, and spec detail. Use just run-once to bind the loopback API, execute one live scheduler pass, print the outcome or error, and exit without entering the forever loop.

API

Run the loopback API:

cargo run --locked -- api --bind 127.0.0.1:7777

If api --bind is omitted, the API uses [api].bind, which defaults to 127.0.0.1:7373. Non-loopback binds are refused unless [api].allow_non_loopback_bind = true.

The stable UI contract is separate from the operational dashboard payload:

  • GET /v1/state returns a redacted JSON snapshot with schema_version, generated_at, event_cursor, parallelism, counters and cost totals, disabled tools, repo lock/status data, active role runs, role runs requiring recovery, task runs, and pipeline status fields. Use this for browser bootstrap and polling instead of depending on /dashboard.
  • GET /v1/ws upgrades to a WebSocket state stream. The first text frame is always an event envelope with event_type: "snapshot" and the same state shape as /v1/state in payload. Later frames are ordered by monotonic id and report worker start/stop, role-run updates, task-run updates, scheduler ticks and wakes, repo status updates, pipeline transitions, and counter/cost changes.
  • WebSocket clients may pass ?cursor=<event_id> when reconnecting. Replay is bounded; if the requested cursor is older than the retained window, the stream sends a resync_required event after the initial snapshot.
  • Local browser development origins http://localhost:5173 and http://127.0.0.1:5173 may call /v1/state, send OPTIONS preflights, and connect to /v1/ws. Other origins require explicit API/CORS configuration rather than a wildcard policy.

Dashboard and janitor

Print operational snapshots without env mode switches:

cargo run --locked -- dashboard
cargo run --locked -- janitor

OpenAPI MCP tooling setup

Run the API and point awslabs.openapi-mcp-server at the generated OpenAPI spec:

cargo run --locked -- api --bind 127.0.0.1:7777

Then start the MCP tooling bridge:

API_NAME=droid_orchestrator_control_api \
API_BASE_URL=http://127.0.0.1:7777 \
API_SPEC_URL=http://127.0.0.1:7777/openapi.json \
LOG_LEVEL=info \
ENABLE_PROMETHEUS=false \
ENABLE_OPERATION_PROMPTS=true \
uvx awslabs.openapi-mcp-server

Bridge tool names are derived from OpenAPI operationId values. The bridge-facing operations keep stable ids such as getIssue, reportRunPullRequest, completeForReview, and failRun.

Real smoke validation

Run feature-gated real smoke validation with the required smoke Linear environment variables:

cargo nextest run --test smoke --features smoke --locked

Safety model

  • Scheduler approval gate: implementer workers are admitted only after Bryan approval, except for the trivial direct lane when the issue is low risk, unambiguous, and has scriptable or directly observable acceptance criteria.
  • Priority classification: when an issue with no Linear priority is selected by the unified scheduler score, priority classification is admitted before that issue's intake, difficulty, docs, spec, or implementation work; already-prioritized issues are never overwritten.
  • Dependency ordering: Linear parents, children, blockedBy relations, and repository locks determine which issues can run.
  • Repo-lock ordering: within the same dependency/lifecycle lane and project-plus-issue priority group, an issue that already owns an active orchestrator-lock is ordered before non-lock-holders. This is a scheduler ordering rule only and does not rewrite Linear priority metadata.
  • Max parallel limits: a global worker ceiling, a shared CPU-heavy budget, and the global model total_weight budget limit worker admission.
  • Worker sandboxing: Linear write tools are disabled for workers through the selected harness. Codex workers stay on --sandbox workspace-write; roles that need command egress receive role-scoped sandbox_workspace_write.network_access=true, and spec/docs research roles also receive web_search="live". With the current Droid harness this is droid exec --disabled-tools.
  • Token hygiene: the API binds to loopback by default, secrets are redacted, write/read paths avoid leaking tokens, and DROID_ORCHESTRATOR_CAPABILITY_TOKEN is delivered only to workers.
  • Repo worktrees: repo-backed implementer runs materialize under ~/.droid-orchestrator/worktrees/<repo-slug>/<linear-branch-name>. The Linear branch name remains the branch identity, so branch names containing / become nested path components under the repo namespace.
  • Test validation: completed work must be commit-on-top, produce a non-empty diff, leave a clean worktree, report a PR, use a sanitized environment, and record Linear pass/fail evidence under the test-validation step.
  • Scripted merge gate: during test-validation, the orchestrator runs the configured validation bundle within a timeout, defaulting to just validate. The bundle includes repo validation plus security scans such as gitleaks; any failing command blocks merge.
  • Post-merge cleanup: after GitHub confirms a PR is merged, Compose projects and volumes in the implementer worktree are stopped, then the implementer worktree is removed before the issue's orchestrator-lock Linear label is cleared. The repo completion ledger is issue_repo_runs(issue_id, repo_slug): each completed repo-backed implementation records one row for that Linear issue and repository. Multi-repo issues repeat implementation for each labeled repo that does not yet have a ledger row, then close automatically when all labeled repos are complete; otherwise cleanup returns the issue to ready scheduling for remaining repo work. Same-repo implementation remains blocked until this cleanup releases the repo lock.

Dashboards include pending_approval_count, rate_limit_deferred_count, strict parallelism shape including workers and total_weight_budget, role runs, disabled tools, task runs, approvals, and test-validation records. Dry-run output reports active Linear repo lock blockers derived from orchestrator-lock labels.

Development and validation

Run the validator before handing off changes:

just validate

just validate includes preflight checks, strict dry-run scheduler validation, a gitleaks security scan, formatting checks, clippy, and nextest. The scripted merge gate treats any failure in that bundle as a merge blocker.

During focused development, use the underlying steps:

just fmt
just check
just test

These run cargo fmt --all --check, cargo clippy --all-targets --all-features --locked -- -D warnings, and cargo nextest run --all-targets --all-features --locked respectively.

just test starts the worktree's Docker Compose MySQL service. MySQL publishes on a random loopback host port, and Rust test support discovers that port with docker compose port mysql 3306, so branch worktrees can validate concurrently without fixed host port collisions. Tests use MySQL only for the remaining workflow-evidence tables; runtime replay guards, capability-token checks, dashboard counters, and repo locks are process-local or Linear-backed.

Use cargo nextest run --all-targets --locked when you need the nextest suite used by orchestration workflows.

Deploy locally with launchd and Docker Compose

  1. Copy env and TOML templates and set values:
cp env.example .env
mkdir -p ~/.droid-orchestrator
cp droid-orchestrator.example.toml ~/.droid-orchestrator/config.toml
  1. Set at least LINEAR_API_KEY in .env.
  2. Set tool paths such as [paths].droid_bin and [paths].gh_bin in ~/.droid-orchestrator/config.toml when defaults are not correct for the container.
  3. Ensure host paths exist and are authenticated:
    • ~/.factory
    • ~/.config/gh
    • ~/code
  4. Deploy from ~/code/droid-orchestrator:
just deploy

just deploy requires a clean checkout, runs git pull --rebase --autostash and git push, installs launchd/com.satoshiandkin.droid-orchestrator.plist into ~/Library/LaunchAgents/, restarts the LaunchAgent, and waits until the app logs started API server. The LaunchAgent runs the same default command as an interactive just invocation from ~/code/droid-orchestrator.

  1. Tail logs:
just logs
  1. Stop the deployed process:
just stop
  1. Stop the deployed process and Docker Compose services:
just down
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment