Skip to content

Repository files navigation

Orca (Dev Orchestra)

Orca is a self-hosted software-development team controlled from Slack. It routes requests to specialist roles, keeps conversational context local, selects local or hosted models by cost and risk, and delegates implementation to isolated OpenHands workers that can edit repositories, run checks, push branches, and open pull requests.

Slack
  -> n8n (public webhook and exact-byte relay)
  -> Dev Orchestra controller (context, routing, leases, progress)
       -> Bifrost -> Ollama or OpenRouter
       -> OpenHands coding workers
       -> GitHub issues, branches, pull requests, and checks

GitHub is the durable source of truth. Slack is the human interface. Redis provides event idempotency and exclusive issue leases. Worker jobs and trajectories are retained locally on the worker host.

Capabilities

  • PM, frontend, backend, QA, reviewer, security, release, and observability roles
  • immediate Slack acknowledgement and periodic long-running-task updates
  • untagged replies inside existing Dev Orchestra threads
  • recent channel and complete thread context
  • channel-to-repository mapping by configured ID, GitHub URL in the channel topic, or exact channel/repository name
  • automatic tracking of GitHub repository renames and transfers
  • issue ownership labels and one-worker-per-issue leases
  • two configurable concurrent OpenHands coding jobs
  • isolated, issue-scoped repository workspaces
  • repository edits, validation, commits, branch pushes, and PR creation
  • deterministic GitHub actions for explicit commands such as close PR #30
  • local Ollama and cost-controlled OpenRouter model tiers through Bifrost
  • durable worker queue recovery after service restarts
  • safe Slack errors while detailed trajectories remain local

Deployment profiles

Local control-plane evaluation

docker compose up starts the controller, n8n, PostgreSQL, and Redis. This is useful for evaluating routing and webhooks, but it is not a complete coding deployment: Bifrost and an OpenHands worker must still be supplied separately.

Full self-hosted deployment

The production layout uses separate controller and worker hosts. Containers, VMs, or Proxmox LXCs all work; they only need private network connectivity.

Component Suggested resources Required software
Controller 2 CPU, 4 GB RAM Debian, Python 3.11+, Redis
n8n 2 CPU, 2-4 GB RAM n8n, public HTTPS ingress
Bifrost 2 CPU, 2-4 GB RAM Bifrost and provider credentials
Coding worker 4 CPU, 12 GB RAM Debian, Docker, Python, OpenHands
Ollama, optional GPU or 8+ CPU Ollama and a coding-capable model

Use static private addresses or internal DNS names. Do not expose the controller, Redis, Bifrost, Docker socket, or worker API directly to the internet.

Prerequisites

  • a GitHub account and repositories the team may modify
  • a fine-grained GitHub token with:
    • Contents: read and write
    • Issues: read and write
    • Pull requests: read and write
    • Metadata: read
    • Actions: read
  • a Slack workspace where you can create and reinstall an app
  • a public HTTPS hostname for the n8n Slack webhook
  • a Bifrost endpoint compatible with the OpenAI chat-completions API
  • either:
    • Ollama for local inference, or
    • OpenRouter/provider credentials configured in Bifrost
  • Linux hosts, VMs, or LXCs for the controller and coding worker

1. Clone and configure

git clone https://github.com/samirank/orca.git
cd dev-orchestra
cp .env.example .env

Generate independent secrets:

openssl rand -hex 32
openssl rand -hex 32
openssl rand -hex 32

Use different values for ORCHESTRA_WEBHOOK_SECRET, GITHUB_WEBHOOK_SECRET, and WORKER_TOKEN. Never commit .env, tokens, Slack secrets, Bifrost keys, or OpenRouter keys.

The important settings are:

Variable Purpose
SLACK_SIGNING_SECRET verifies Slack's exact request body
SLACK_BOT_TOKEN reactions, replies, channel and thread context
GITHUB_TOKEN issues, branches, commits, and PRs
BIFROST_BASE_URL internal model-gateway URL, without /v1
OLLAMA_MODEL local PM, classification, and summary model
CHEAP_MODEL routine coding model
BALANCED_MODEL difficult implementation/debugging model
PREMIUM_MODEL high-risk or capped escalation model
CONTEXT_LOCAL_ONLY forces conversational Slack context to the local model
WORKER_URL private worker API, normally port 8001
WORKER_TOKEN controller-to-worker bearer secret
GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL verified GitHub identity for agent commits

Use a GitHub-verified email. GitHub's account-specific noreply address is recommended because it preserves privacy while allowing GitHub and Vercel to associate commits with the authorized account.

2. Configure model routing

Configure Bifrost so the model names in .env resolve successfully. A typical setup is:

OLLAMA_MODEL=ollama/your-local-model
CHEAP_MODEL=openrouter/your-low-cost-coding-model
BALANCED_MODEL=openrouter/your-balanced-model
PREMIUM_MODEL=openrouter/your-premium-model

The PM intent compiler always uses OLLAMA_MODEL. Normal conversation also stays local when CONTEXT_LOCAL_ONLY=true. If the user explicitly selects a hosted tier, or PM selects one for a specialist task, the specialist receives the current task but not the complete Slack history. Coding workers receive only the task and repository workspace plus the selected model.

Verify Bifrost before continuing:

curl -fsS "$BIFROST_BASE_URL/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $BIFROST_API_KEY" \
  -d '{"model":"YOUR_MODEL","messages":[{"role":"user","content":"reply ok"}]}'

3. Create the Slack app

Create an app from slack/manifest/manifest.yaml and replace N8N_PUBLIC_HOST with your public n8n hostname.

Required bot scopes:

  • app_mentions:read
  • channels:history
  • channels:read
  • groups:history
  • groups:read
  • im:history
  • chat:write
  • commands
  • reactions:write

Required bot events:

  • app_mention
  • message.channels
  • message.groups
  • message.im
  • member_joined_channel

Set the Event Subscriptions request URL to:

https://YOUR_N8N_HOST/webhook/slack-events

After adding scopes or events, save the configuration and reinstall the app to the workspace. Copy the Bot User OAuth Token and Signing Secret into the controller environment.

Do not enable Socket Mode when using the public n8n event URL.

4. Configure n8n

Import:

n8n/workflows/slack-command-bus.json

The webhook node must retain Raw Body, and the HTTP relay must send the binary data field. Slack signs the original request bytes; parsing and re-serializing JSON invalidates signatures for some messages.

Update the relay URL if your controller address differs from the checked-in example:

http://CONTROLLER_PRIVATE_ADDRESS:8000/v1/slack/events

Activate the workflow and confirm Slack reports the request URL as verified.

5. Install the controller

Copy the checkout to /opt/dev-orchestra on the controller host, install Redis and Python, then run:

sudo apt update
sudo apt install -y python3 python3-venv redis-server
sudo bash /opt/dev-orchestra/deploy/install-lxc.sh

Edit /etc/dev-orchestra/orchestra.env with your values. At minimum, set:

ORCHESTRA_ENV=production
SLACK_SIGNING_SECRET=...
SLACK_BOT_TOKEN=xoxb-...
GITHUB_TOKEN=github_pat_...
BIFROST_BASE_URL=http://BIFROST_PRIVATE_ADDRESS:8080
WORKER_URL=http://WORKER_PRIVATE_ADDRESS:8001
WORKER_TOKEN=...
REDIS_URL=redis://127.0.0.1:6379/0

Restart and verify:

sudo systemctl restart dev-orchestra
curl -fsS http://127.0.0.1:8000/healthz
curl -fsS http://127.0.0.1:8000/readyz

6. Install the coding worker

The worker is intentionally isolated from the controller. Install Docker first. On Proxmox, a Debian Docker LXC created with the Proxmox VE Helper-Scripts works well. Use an unprivileged container with nesting enabled and keep it on a private network.

Install OpenHands:

curl -LsSf https://astral.sh/uv/install.sh -o /tmp/install-uv.sh
sh /tmp/install-uv.sh
/root/.local/bin/uv tool install openhands --python 3.12

Copy the checkout to /opt/dev-orchestra, then run:

sudo apt update
sudo apt install -y python3 python3-venv git
sudo bash /opt/dev-orchestra/deploy/install-worker.sh

Create /etc/dev-orchestra/worker.env:

ORCHESTRA_ENV=production
GITHUB_TOKEN=github_pat_...
GITHUB_OWNER=your-github-user-or-org
BIFROST_BASE_URL=http://BIFROST_PRIVATE_ADDRESS:8080
BIFROST_API_KEY=
OLLAMA_MODEL=ollama/your-local-model
CHEAP_MODEL=openrouter/your-low-cost-coding-model
BALANCED_MODEL=openrouter/your-balanced-model
PREMIUM_MODEL=openrouter/your-premium-model
WORKER_TOKEN=the-same-secret-used-by-the-controller
WORKER_MAX_CONCURRENCY=2
WORKER_DATA_DIR=/srv/dev-orchestra
GIT_AUTHOR_NAME=Your GitHub Name
GIT_AUTHOR_EMAIL=your-verified-github-noreply-address

Restart and verify:

sudo systemctl restart dev-orchestra-worker
curl -fsS http://127.0.0.1:8001/healthz

Worker task checkouts are stored beneath /srv/dev-orchestra/tasks; internal OpenHands trajectories are stored beneath /srv/dev-orchestra/logs.

Configure each agent

Public role defaults live in agents/<role>.md for pm, frontend, backend, security, qa, reviewer, release, and observability, deployed through AGENT_DEFAULTS_DIR (default: /opt/dev-orchestra/agents). Private installation overrides live outside Git in AGENT_CONFIG_DIR (default: /var/lib/dev-orchestra/agents). Repository-specific AGENTS.md instructions are applied last.

Authorized Slack administrators can manage private overrides:

agents list
agents show security
agents edit security: Always run gitleaks and dependency review.
agents approve deadbeef
agents reject deadbeef
agents history security
agents reset security

Edits are secret-scanned, presented as a diff, and remain pending until approved. Approval synchronizes the controller and coding worker, records an audit event under AGENT_ADMIN_DIR, and never commits the private override to Git. Authorization is derived from Slack workspace administration status (is_admin or workspace owner) through the users.info API. Add the users:read bot scope and reinstall the Slack app after changing scopes.

The reviewer instructions are always executed in a separate OpenHands session. That session may report findings but is rejected if it modifies implementation files.

Enable controlled web research

Set WEB_SEARCH_URL to a SearXNG-compatible base URL, for example:

WEB_SEARCH_URL=http://searxng:8080
WEB_SEARCH_MAX_RESULTS=5

When a task explicitly asks for current documentation, releases, CVEs, web research, or external sources, Orca fetches search results and supplies them as untrusted reference material. Web content is never treated as an instruction.

Pull request readiness and escalation

Orca opens coding pull requests as drafts. Before marking one ready, it:

  1. formats changed files using the repository's configured scripts;
  2. runs discoverable lint, type-check, test, and build gates;
  3. invokes the independent reviewer agent;
  4. waits for every reported GitHub check to pass;
  5. records validation and review evidence in the PR body.

If implementation, formatting, tests, or review fail, Orca escalates through the configured model tiers up to MAX_ESCALATIONS. Exhausted attempts produce a structured failure report in the worker logs and as a GitHub issue comment. Set REMOTE_CHECK_TIMEOUT_SECONDS to control the GitHub wait and REQUIRE_REMOTE_CHECKS=false only for repositories that intentionally have no CI.

Concurrent push safety

Orca permits only one active worker per repository issue. Before every push it checks the remote branch tip. If Cursor, a human, or another bot pushed first, Orca identifies the GitHub actor and commit, fetches and rebases without overwriting either history, reruns quality gates and independent review, then retries with --force-with-lease. The default is three guarded attempts (PUSH_RETRY_COUNT). Conflicts stop safely and are reported with the actor, branch, commit, timestamp, and recovery steps.

7. Connect a Slack conversation to a repository

Invite Orca to a channel, or open its direct-message tab, then send:

@Orca settings repository your-org/your-repo

You can paste a GitHub repository URL instead. Orca validates access and stores the connection in Redis under that Slack conversation ID, so it survives restarts and does not require an environment variable, JSON mapping, or channel topic. Each channel or direct message can point to a different repository.

Manage everything from Slack:

@Orca settings
@Orca settings model automatic
@Orca settings context local
@Orca settings test
@Orca settings reset

automatic lets PM select a model for each task. Explicit phrases such as using premium still override the conversation default. GitHub renames and transfers are followed and the stored canonical repository name is updated automatically. For a completely different repository, run the repository settings command again. Secrets are intentionally not accepted through chat.

8. Test the complete workflow

Invite Orca to the Slack channel and send:

@Orca implement issue #123 and open a PR

Expected behavior:

  1. immediate 👀 reaction and acknowledgement
  2. role and model-routing update
  3. GitHub issue claim and exclusive lease
  4. periodic worker phase updates
  5. repository edit and validation
  6. branch push and PR link

After the initial mention, reply normally inside the thread without tagging the bot. Ask what's the progress? to retrieve current worker state.

PM is the default entry agent. It uses the local model to compile ordinary language and Slack thread context into a validated action plan, then delegates to a specialist when appropriate. Invoke a role directly by placing it first:

@Orca PM, process the reporter findings and update the backlog.
@Orca backend, work on issue #32.
@Orca security, audit issue #41 using premium.

Choose a configured model tier with using local, using cheap, using balanced, or using premium. PM may also choose a tier based on task complexity and risk. Send @Orca help for the full command guide, including natural-language examples and the currently configured models.

For destructive GitHub actions, use explicit targets:

close PR #30

If a phrase such as close it is ambiguous, Dev Orchestra asks which PR rather than guessing.

Local development

python3 -m venv .venv
. .venv/bin/activate
pip install -e '.[dev]'
pytest -q
ruff check .

For control-plane evaluation:

docker compose up --build

Operations

# Controller
systemctl status dev-orchestra
journalctl -u dev-orchestra -f

# Worker
systemctl status dev-orchestra-worker
journalctl -u dev-orchestra-worker -f

# Routing policy
curl -fsS http://CONTROLLER:8000/v1/policy

Back up:

  • n8n database and encryption key
  • Redis if persistent leases must survive host loss
  • /etc/dev-orchestra/*.env
  • /srv/dev-orchestra/jobs.json
  • any external Bifrost/provider configuration

Rotate Slack, GitHub, worker, webhook, n8n, and provider secrets independently.

Troubleshooting

Tagged messages work, but ordinary thread replies do not

Confirm message.channels, message.groups, and message.im are subscribed, then reinstall the Slack app.

Controller reports invalid Slack signature

Enable Raw Body on the n8n Webhook node and relay the binary data field. Do not use JSON.stringify($json.body).

Worker says no changes were produced

Inspect /srv/dev-orchestra/logs/JOB_ID.jsonl. The wrapper supports both OpenHands-created commits and uncommitted changes.

Vercel cannot verify the commit author

Set GIT_AUTHOR_EMAIL to an email verified on the GitHub account that has Vercel team access. GitHub's account-specific noreply address is recommended.

Repository was renamed

No action is normally required. Orca follows GitHub's canonical repository redirect and updates its saved setting. For a replacement repository, send @Orca settings repository owner/new-repository.

Security model

  • Slack signatures are checked against the exact original request bytes.
  • n8n and all internal APIs remain on a private network.
  • worker requests require a separate bearer secret.
  • GitHub webhook, Slack, worker, and provider secrets are independent.
  • GitHub actions with destructive effects require an explicit target.
  • coding work is isolated by task workspace and exclusive issue lease.
  • hosted models receive task-specific context, not complete Slack history, when CONTEXT_LOCAL_ONLY=true.
  • detailed execution logs remain local and are not dumped into Slack.
  • production merging and deployment remain subject to repository checks and protection rules.

Known limitations

  • Genuine separate Slack handles such as @pm and @backend require separate Slack apps/bot identities. The current deployment identifies roles in one bot's replies.
  • Job persistence is local to the worker host; it is not yet a distributed scheduler.
  • Concurrency is per worker process and configured with WORKER_MAX_CONCURRENCY.
  • Automatic merge/release promotion is intentionally conservative.
  • The Proxmox LXC creation itself is environment-specific and is not performed by the checked-in install scripts.

See ARCHITECTURE.md, docs/OPERATIONS.md, and the policy files under policies/ for additional design and operational context.

Releases

Packages

Contributors

Languages