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.
- 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
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.
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.
- 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
git clone https://github.com/samirank/orca.git
cd dev-orchestra
cp .env.example .envGenerate independent secrets:
openssl rand -hex 32
openssl rand -hex 32
openssl rand -hex 32Use 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.
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-modelThe 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"}]}'Create an app from slack/manifest/manifest.yaml
and replace N8N_PUBLIC_HOST with your public n8n hostname.
Required bot scopes:
app_mentions:readchannels:historychannels:readgroups:historygroups:readim:historychat:writecommandsreactions:write
Required bot events:
app_mentionmessage.channelsmessage.groupsmessage.immember_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.
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.
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.shEdit /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/0Restart and verify:
sudo systemctl restart dev-orchestra
curl -fsS http://127.0.0.1:8000/healthz
curl -fsS http://127.0.0.1:8000/readyzThe 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.12Copy 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.shCreate /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-addressRestart and verify:
sudo systemctl restart dev-orchestra-worker
curl -fsS http://127.0.0.1:8001/healthzWorker task checkouts are stored beneath /srv/dev-orchestra/tasks; internal
OpenHands trajectories are stored beneath /srv/dev-orchestra/logs.
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.
Set WEB_SEARCH_URL to a SearXNG-compatible base URL, for example:
WEB_SEARCH_URL=http://searxng:8080
WEB_SEARCH_MAX_RESULTS=5When 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.
Orca opens coding pull requests as drafts. Before marking one ready, it:
- formats changed files using the repository's configured scripts;
- runs discoverable lint, type-check, test, and build gates;
- invokes the independent reviewer agent;
- waits for every reported GitHub check to pass;
- 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.
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.
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.
Invite Orca to the Slack channel and send:
@Orca implement issue #123 and open a PR
Expected behavior:
- immediate 👀 reaction and acknowledgement
- role and model-routing update
- GitHub issue claim and exclusive lease
- periodic worker phase updates
- repository edit and validation
- 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.
python3 -m venv .venv
. .venv/bin/activate
pip install -e '.[dev]'
pytest -q
ruff check .For control-plane evaluation:
docker compose up --build# 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/policyBack 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.
Confirm message.channels, message.groups, and message.im are subscribed,
then reinstall the Slack app.
Enable Raw Body on the n8n Webhook node and relay the binary data field.
Do not use JSON.stringify($json.body).
Inspect /srv/dev-orchestra/logs/JOB_ID.jsonl. The wrapper supports both
OpenHands-created commits and uncommitted changes.
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.
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.
- 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.
- Genuine separate Slack handles such as
@pmand@backendrequire 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.