Helix is an autonomous incident response platform. It takes a production crash from Sentry or Rollbar all the way to a ready-to-merge pull request in under 10 minutes — no human involvement required until the PR review.
Sentry / Rollbar crash → Crash Handler → QA Agent → Dev Agent → PR + Notifications
↓
Human Approval (Slack)
- Crash Handler — receives Sentry or Rollbar webhooks, classifies severity, and produces a structured crash report
- QA Agent — deduplicates against open GitHub Issues, generates a TDD test that asserts the correct behaviour (not that the crash occurs), validates the test and retries if needed. Supports Python, JavaScript/TypeScript, Ruby, and Java/Kotlin.
- Dev Agent — generates a fix suggestion and posts it to the GitHub Issue, then runs a full TDD loop: clones the repo, runs the failing test, writes the minimum fix, verifies the full suite; retries up to 3 times before escalating
- Notifier Agent — handles all outbound Slack and email notifications: fix suggestions, PR links, Slack Approve/Reject buttons for human approval, and escalation alerts when the Dev Agent exhausts retries
Agents communicate via Redis Streams (or AWS EventBridge). Shared state lives in Redis, keyed by incident_id.
agents/
crash_handler/ FastAPI webhook server — receives Sentry/Rollbar events, hosts the dashboard API
agent.py Core logic: LLM analysis → CrashReport
prompts.py System + user prompt templates
main.py GET / (landing page), POST /webhook/sentry, POST /webhook/rollbar, POST /slack/actions
GET /api/incidents, GET /api/incidents/{id}, GET /api/stream/{id} (SSE)
GET/POST/DELETE /api/repos — per-user repo configuration
GET/POST /api/projects, PUT /api/projects/{owner}/{name}/settings,
DELETE /api/projects/{owner}/{name} — per-project credential settings
GET/PUT /api/settings — account-level LLM keys (Anthropic, OpenRouter, Ollama)
GET /api/me — caller identity from JWT
GET /app/* (serves the React dashboard)
railway.json Railway service config for this agent
qa/ Subscribes to crash_analysed
agent.py GitHub Issues deduplication, repo clone, LLM test generation + validation
prompts.py Correct-behaviour test prompt + rejection_note for retries
main.py Entry point: subscriber loop
railway.json Railway service config for this agent
dev/ Subscribes to test_case_generated
agent.py LLM fix suggestion → GitHub comment → TDD loop → PR or escalate
prompts.py build_suggestion() for API call; build_tdd() for claude-code CLI
main.py Entry point: subscriber loop
railway.json Railway service config for this agent
notifier/ Subscribes to fix_suggested and fix_failed
agent.py Slack + email for fix suggestions, PR approvals, and escalations
main.py Entry point: two concurrent subscriber loops
railway.json Railway service config for this agent
core/
config.py Typed config loaders for all agents and integrations
events.py Redis Streams / Pub/Sub / EventBridge publish and subscribe helpers
state.py Redis read/write helpers, keyed by incident_id (incidents + user repo/project configs)
models.py Pydantic models shared across all agents (CrashReport, QAResult, PRResult, RepoConfig, Project, ProjectSettings)
llm.py Routes to Anthropic SDK, OpenRouter, Ollama, Claude Code CLI, or OpenCode CLI; instruments every call with LangSmith + Langfuse tracing and OTel spans
telemetry.py OpenTelemetry setup — call setup_tracing() once at startup; no-op when OTEL_ENABLED is not true
permissions.py Per-agent tool access control — declare and enforce at runtime
ui_events.py Dashboard event publishing — agent progress + tool call events persisted to Redis + forwarded via Pub/Sub
auth.py Auth0 JWT validation (RS256 via JWKS) — optional, falls back to demo user
utils.py extract_json() — parses structured JSON from LLM output
db.py Async Postgres helpers — projects, github_installations, per-project settings, user_settings tables
github_app.py GitHub App JWT generation, installation access token fetch/cache, repo listing
integrations/
sentry.py HMAC-SHA256 signature verification + Sentry webhook payload parsing
rollbar.py Access token verification + Rollbar webhook payload parsing
github.py Git CLI wrappers (clone, branch, commit, push) + GitHub REST API
jira.py JIRA REST API v3 — create and update issues
slack.py Slack Web API — notifications, approval buttons, and escalation alerts
email.py SendGrid API (preferred) or SMTP fallback
dashboard/ React + TypeScript + Tailwind — streaming incident dashboard
src/
App.tsx Root app with React Router (base: /app/) — Incidents + Projects + Repos nav
api.ts fetch wrappers, EventSource subscription, Auth0 token injection
main.tsx Entry point — wraps app in Auth0Provider when VITE_AUTH0_DOMAIN is set
pages/
IncidentList.tsx Polls /api/incidents — table of all incidents, newest first
IncidentDetail.tsx Opens SSE stream — pipeline progress, tool calls, live log, crash/QA/PR details
Projects.tsx Project management — create projects, configure per-project credentials (API keys, tokens)
Repos.tsx Repo configuration — add / remove repos per user
Settings.tsx Account-level LLM key settings — Anthropic, OpenRouter, Ollama base URL
components/
PipelineProgress.tsx 4-step pipeline tracker with checkmarks
StreamPanel.tsx Auto-scrolling live agent activity log
ToolTimeline.tsx Live tool call log — LLM, GitHub, Git, Claude Code calls with status
StatusBadge.tsx Pill badge for pipeline status
SeverityBadge.tsx Pill badge for crash severity
AuthGuard.tsx Redirects unauthenticated users to Auth0 login
NavUserChip.tsx Avatar + name + sign-out button in nav bar
TokenProviderBridge.tsx Registers Auth0 token-getter with the API client
.env.example Frontend env var template (VITE_AUTH0_DOMAIN, VITE_AUTH0_CLIENT_ID, etc.)
vite.config.ts Base /app/, proxies /api → localhost:8000 in dev
evals/
datasets.py Sample inputs for each agent eval — uploaded to LangSmith datasets
evaluators.py Heuristic evaluator functions (no LLM calls) — score each agent's output
run.py CLI runner — uploads datasets, runs evals, prints pass/fail summary
config.yaml Source of truth for all non-secret config (models, Redis, permissions, LangSmith)
index.html Landing page — served at GET /, Sign In CTA routes to /app
scripts/
close_all.py Close all open PRs and issues in a repo (uses GITHUB_TOKEN)
pyproject.toml Python package definition and dependencies
uv.lock Pinned dependency lockfile
.env.example Template for all required environment variables
railway-deploy.sh One-command multi-service deployment to Railway
docs/
PRD.md Full product requirements
architecture.md System design, event schemas, hosting options
features.md Complete feature reference
SAAS.md SaaS architecture — tenant isolation models, scaling, delivery phases
TECHDECISIONS.md Technical decision log — TD-001 (shared streams), TD-002 (Anthropic gate), TD-003 (Ollama BYOK)
plans/ Implementation plans for in-progress work
- Python 3.12+
- uv — Python package manager
- pnpm — Node.js package manager (dashboard only)
- Redis (local or cloud — see Docker section)
- Claude Code CLI or OpenCode CLI — required for the Dev Agent (
claude-codeoropencodeprovider)
1. Install Python dependencies
uv sync --extra dev2. Configure the target repository
Open config.yaml and set github.target_repo to the repo Helix will fix bugs in:
github:
target_repo: "your-org/your-repo"3. Set environment variables
cp .env.example .env
# edit .env and fill in all required valuesRequired variables:
| Variable | Description |
|---|---|
ANTHROPIC_API_KEY |
Anthropic API key (crash handler, QA agents) |
REDIS_URL |
Redis connection URL — leave unset when using local Docker Redis |
SENTRY_WEBHOOK_SECRET |
Sentry client secret — used for HMAC-SHA256 signature verification |
ROLLBAR_ACCESS_TOKEN |
Rollbar project read token — verified against each webhook payload |
GITHUB_TOKEN |
GitHub personal access token with repo + issues scope — used as fallback when no GitHub App installation token is available |
GITHUB_APP_ID |
Numeric GitHub App ID (Phase 3+) |
GITHUB_APP_PRIVATE_KEY |
GitHub App RSA private key, base64-encoded (Phase 3+) |
GITHUB_APP_SLUG |
GitHub App slug for building the installation URL, e.g. helix-bot (Phase 3+) |
DATABASE_URL |
Postgres connection URL — required for Phase 3 per-project config and GitHub App token caching |
SLACK_BOT_TOKEN |
Slack bot token (xoxb-...) with chat:write scope (optional — logs warning if absent) |
SLACK_SIGNING_SECRET |
Slack app signing secret — from app settings → Basic Information |
SLACK_APPROVAL_CHANNEL |
Channel ID or name for approval messages (optional) |
SENDGRID_API_KEY |
SendGrid API key with Mail Send permission (preferred over SMTP) |
SMTP_HOST |
SMTP server fallback, e.g. smtp.gmail.com (used if SendGrid key not set) |
SMTP_USER |
SMTP username |
SMTP_PASSWORD |
SMTP password or app password |
EMAIL_FROM |
Sender address, e.g. helix@acme.com (optional — logs warning if absent) |
EMAIL_TO |
Comma-separated recipients, e.g. oncall@acme.com (optional) |
LANGSMITH_API_KEY |
LangSmith API key — enables LLM call tracing and the eval suite (optional; tracing disabled if unset) |
LANGSMITH_PROJECT |
LangSmith project name (default: helix) |
LANGSMITH_TRACING |
Set to true to enable LangSmith tracing in core/llm.py (requires LANGSMITH_API_KEY) |
LANGFUSE_SECRET_KEY |
Langfuse secret key — enables Langfuse LLM observability (optional) |
LANGFUSE_PUBLIC_KEY |
Langfuse public key (required alongside LANGFUSE_SECRET_KEY) |
LANGFUSE_HOST |
Langfuse server URL (default: https://cloud.langfuse.com; set for self-hosted) |
OTEL_ENABLED |
Set to true to enable OpenTelemetry distributed tracing across all agents |
OTEL_EXPORTER_OTLP_ENDPOINT |
OTLP/gRPC endpoint to export traces to (default: http://localhost:4317) |
OTEL_SERVICE_NAME |
OTel service name tag on all spans (default: helix) |
HELIX_DEMO |
Set to true to skip webhook signature/token verification — local testing only; default is false |
AUTH0_DOMAIN |
Auth0 tenant domain, e.g. your-tenant.auth0.com (optional — leave unset for demo mode) |
AUTH0_AUDIENCE |
Auth0 API identifier, e.g. https://api.helix.yourapp.com (required when AUTH0_DOMAIN is set) |
Slack, email, and Auth0 are optional — missing configuration is logged as a warning and the pipeline/dashboard continues. See .env.example for the full list.
The fastest way to run the full stack locally. Requires Docker and Docker Compose.
1. Configure secrets
cp .env.example .env
# edit .env and fill in all required values2. Set the target repository in config.yaml
github:
target_repo: "your-org/your-repo"3. Start everything
docker compose up --buildRedis starts automatically. If REDIS_URL in .env points to localhost, it is automatically redirected to the Docker Redis container.
To use an external Redis (Redis Cloud, AWS ElastiCache, etc.), set REDIS_URL to a non-localhost URL in .env before starting.
This starts all four agents. The crash handler is available at http://localhost:8000 with endpoints at /webhook/sentry and /webhook/rollbar.
Individual agent logs
docker compose logs -f dev
docker compose logs -f crash_handlerRebuild after code changes
docker compose up --buildStop the stack
docker compose downSentry and Rollbar need a public HTTPS URL to POST webhook events to. For local development, use ngrok to tunnel your local port.
1. Install ngrok
brew install ngrok2. Start the tunnel
ngrok http 8000ngrok output will look like this:
Account 88Hours (Plan: Free)
Version 3.37.3
Region Australia (au)
Latency 20ms
Web Interface http://127.0.0.1:4040
Forwarding https://carroty-cris-uncravingly.ngrok-free.app -> http://localhost:8000
3. Configure the webhook in Sentry or Rollbar
Sentry: Project Settings → Integrations → Webhooks, set the URL to:
https://carroty-cris-uncravingly.ngrok-free.app/webhook/sentry
Rollbar: Settings → Notifications → Webhook, set the URL to:
https://carroty-cris-uncravingly.ngrok-free.app/webhook/rollbar
Note: The ngrok URL changes each time you restart ngrok on the free plan. Update the webhook URL whenever it changes, or use a paid ngrok plan with a fixed domain.
4. Monitor live requests
ngrok's web interface at http://127.0.0.1:4040 shows every request and response in real time — useful for debugging webhook payloads.
Each agent runs as its own process. Start them all:
# Crash Handler — webhook server + dashboard API (receives Sentry and Rollbar events)
uv run uvicorn agents.crash_handler.main:app --host 0.0.0.0 --port 8000
# QA Agent — subscribes to crash_analysed
uv run --env-file .env python -m agents.qa.main
# Dev Agent — subscribes to test_case_generated
uv run --env-file .env python -m agents.dev.main
# Notifier Agent — subscribes to fix_suggested and fix_failed
uv run --env-file .env python -m agents.notifier.mainPoint your Sentry webhook at http://<host>:8000/webhook/sentry or Rollbar at http://<host>:8000/webhook/rollbar.
The React dashboard streams live agent activity and shows all incidents at http://localhost:8000/app.
Build for production (required before the dashboard loads from FastAPI):
cd dashboard
pnpm install
pnpm buildThe built files are written to dashboard/dist/ and served automatically by the crash handler at /app.
Local development (Vite dev server with hot reload):
cd dashboard
pnpm install
pnpm dev # http://localhost:5173/appVite proxies /api → http://localhost:8000 during dev, so the FastAPI backend must be running.
Auth0 setup (optional — skip for demo mode)
Without AUTH0_DOMAIN set, the dashboard is accessible without login. To enable GitHub login via Auth0:
- Create an Auth0 tenant and add a Single Page Application (note the Client ID)
- Create an API — the Identifier becomes
AUTH0_AUDIENCE(e.g.https://helix.api); enable User Access for your SPA - Enable the GitHub social connection: Auth0 → Authentication → Social → GitHub
- In your Auth0 Application Settings:
- Allowed Callback URLs:
https://<your-app>.up.railway.app/callback, http://localhost:5173/app/ - Allowed Logout URLs:
https://<your-app>.up.railway.app, http://localhost:5173/app/ - Allowed Web Origins / CORS:
https://<your-app>.up.railway.app, http://localhost:5173
- Allowed Callback URLs:
- Optionally paste
dashboard/login.htmlinto Auth0 → Branding → Universal Login → Custom Login Page for a Helix-branded login screen - Set in your
.env:AUTH0_DOMAIN=your-tenant.auth0.com AUTH0_AUDIENCE=https://helix.api - Create
dashboard/.env.local:VITE_AUTH0_DOMAIN=your-tenant.auth0.com VITE_AUTH0_CLIENT_ID=your-spa-client-id VITE_AUTH0_AUDIENCE=https://helix.api
Send a test payload
Rollbar:
curl -X POST http://localhost:8000/webhook/rollbar \
-H "Content-Type: application/json" \
-d @test_payloads/rollbar_new_item.jsonSentry (signature is verified — set HELIX_DEMO=true in .env to skip verification during local testing):
curl -X POST http://localhost:8000/webhook/sentry \
-H "Content-Type: application/json" \
-H "sentry-hook-signature: <hmac-sha256-of-body>" \
-d @test_payloads/sentry_event.jsonA successful request returns 202 Accepted with an incident_id. Open http://localhost:8000/app to watch the pipeline run live.
Verify the result in Redis
redis-cli GET helix:incident:<incident_id>:crash_report | python3 -m json.tool
redis-cli GET helix:incident:<incident_id>:statusFlush all Helix state from Redis and Postgres. Use $REDIS_URL and $DATABASE_URL from your Railway environment variables or .env.
Redis — nuke all Helix keys:
redis-cli -u "$REDIS_URL" --scan --pattern "helix:*" | xargs redis-cli -u "$REDIS_URL" DELOr flush the entire Redis instance (if it's Helix-only):
redis-cli -u "$REDIS_URL" FLUSHALLPostgres — drop and recreate the public schema:
psql "$DATABASE_URL" -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"Useful for resetting a test repository between runs.
# Dry run — prints what would be closed without making any changes
python scripts/close_all.py owner/repo --dry-run
# Close everything
python scripts/close_all.py owner/repoReads GITHUB_TOKEN from the environment or .env. Requires the token to have repo scope.
uv run pytest161 tests across agents, core, and integrations. All tests use mocks — no live Redis, GitHub, or LLM calls required.
LangSmith evals measure agent output quality. Each eval calls the real LLM and scores the response using heuristic evaluators (no LLM-as-judge, no extra cost beyond the API call itself).
Agents covered: Crash Handler (4 examples), QA Agent (2 examples), Dev Agent (3 examples).
Push datasets only (no LLM calls):
uv run --env-file .env python -m evals.run --dataset-onlyRun all evals:
uv run --env-file .env python -m evals.runRun one agent:
uv run --env-file .env python -m evals.run --agent crash_handler
uv run --env-file .env python -m evals.run --agent qa
uv run --env-file .env python -m evals.run --agent devEvals also run automatically in CI on every push to main and on PRs targeting main (.github/workflows/evals.yml). The CI job tags each experiment with the commit SHA so any LangSmith run is traceable to the exact commit. The job exits non-zero if any agent scores below 0.8, failing the check.
The eval step is skipped automatically if ANTHROPIC_API_KEY or LANGSMITH_API_KEY is not set as a GitHub Actions secret — the job exits 0 with a skip message rather than failing. To enable evals in CI, add both as repository secrets: GitHub → Settings → Secrets and variables → Actions.
LLM call tracing via Langfuse — traces, generations, token costs, and latency. Disabled by default; runs alongside LangSmith if both are configured.
Enable:
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_PUBLIC_KEY=pk-lf-...
# LANGFUSE_HOST=https://your-self-hosted-langfuse.com # omit for cloudEvery call to complete() in core/llm.py logs a helix.<agent> generation with model, provider, prompt, response, and token usage.
End-to-end distributed tracing across all four agents. Disabled by default — zero overhead unless explicitly enabled.
Enable:
OTEL_ENABLED=true
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 # or your backend's endpointSpan hierarchy per incident:
crash_handler.handle_incident (one parent span per webhook)
└── llm.complete (one child span per LLM call)
qa.handle_incident
└── llm.complete
dev.handle_incident
└── llm.complete
notifier.fix_suggested / fix_failed / pr_created / duplicate_detected
Attributes on every span: helix.agent, helix.incident_id, helix.provider, helix.model, helix.input_tokens, helix.output_tokens
Compatible backends: Datadog, Grafana Tempo, Jaeger, Honeycomb, AWS X-Ray — any OTLP/gRPC-compatible receiver.
When OTEL_ENABLED is not set, the OTel API's built-in no-op tracer is used — no packages need to be running, no errors thrown.
| Agent | Default provider | Default model | Why |
|---|---|---|---|
| Crash Handler | Anthropic | claude-haiku-4-5 | Structured extraction — fast and cheap |
| QA Agent | Anthropic | claude-haiku-4-5 | Pattern-matching test generation |
| Dev Agent | Claude Code CLI | claude-sonnet-4-6 | Deep reasoning + full repo file access |
All models are configurable in config.yaml or via HELIX_<AGENT>_PROVIDER / HELIX_<AGENT>_MODEL env vars.
Per-project overrides are set in project_settings.agent_overrides. Account-level API keys are set in the dashboard Settings page (user → Settings) and apply to all projects.
Supported providers:
| Provider | Agents | Key source |
|---|---|---|
anthropic |
All | Account settings → Anthropic API key |
openrouter |
Crash Handler, QA | Account settings → OpenRouter API key |
ollama |
Crash Handler, QA | Project agent_overrides.base_url (customer self-hosted, GPU required) |
claude-code |
Dev Agent only | Anthropic API key (CLI subprocess) |
opencode |
Dev Agent only | No API key required — uses OpenCode CLI with any configured backend (Ollama, GitHub Copilot, etc.) |
Dev Agent requires either an Anthropic API key or the
opencode/claude-codeprovider. Without any of these, the TDD loop is skipped and apr_skippedevent is published. Crash Handler and QA can use OpenRouter or Ollama as a cheaper alternative.
Using OpenCode with Ollama (no Anthropic key required):
# 1. Set the Dev Agent provider
HELIX_DEV_PROVIDER=opencode
# 2. Optional: override the model (otherwise uses OpenCode's configured default)
HELIX_DEV_OPENCODE_MODEL=ollama/gemma4
# 3. Configure Ollama in OpenCode's config (~/.config/opencode/opencode.json)
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"ollama": {
"npm": "@ai-sdk/openai-compatible",
"name": "Ollama (local)",
"options": { "baseURL": "http://localhost:11434/v1" },
"models": {
"gemma4": { "name": "Gemma 4" }
}
}
}
}Each agent declares exactly which tools it is allowed to call. Permissions are enforced at runtime via core/permissions.py — a PermissionDenied exception is raised before any unauthorised operation executes.
| Agent | Can do | Cannot do |
|---|---|---|
| Crash Handler | Write Redis, publish crash_analysed |
GitHub, Slack |
| QA Agent | Read GitHub, write Redis, publish test_case_generated |
Create PRs, push code |
| Dev Agent | Clone repo, push code, create PRs, write Redis | Slack, approve own PRs |
| Notifier | Post to Slack, send email | Write Redis, touch GitHub |
Permissions are declared in config.yaml under the permissions: key for each agent.
| Channel | Published by | Consumed by |
|---|---|---|
helix:events:crash_analysed |
Crash Handler | QA Agent |
helix:events:test_case_generated |
QA Agent | Dev Agent |
helix:events:fix_suggested |
Dev Agent | Notifier Agent |
helix:events:pr_created |
Dev Agent | — |
helix:events:fix_failed |
Dev Agent (on exhaustion) | Notifier Agent |
EventBridge uses the same names as detail-type on the helix-mvp bus. Switch backends with HELIX_EVENT_BACKEND=eventbridge.
The dashboard subscribes to a separate per-incident channel (helix:ui:{incident_id}) for fine-grained live progress events. These are ephemeral — Pub/Sub only, not persisted.
| Backend | When to use |
|---|---|
redis |
Local dev, Railway, any non-AWS deployment (default) |
eventbridge |
AWS Lambda or ECS deployments |
Redis is the default. Within Redis, the event transport can be configured with redis_mode in config.yaml:
| Mode | Behaviour |
|---|---|
streams |
Redis Streams — messages persist across agent restarts (default) |
pubsub |
Redis Pub/Sub — fire-and-forget, messages lost if no subscriber is running |
| Stage | Recommended |
|---|---|
| Local dev | docker compose up --build |
| MVP launch | Railway (one service per agent) + Redis Cloud |
| AWS-native | Lambda per agent + EventBridge + ElastiCache |
The button deploys the crash_handler (the webhook-receiving service) as the primary service. After it's running:
- Set the required environment variables in the Railway dashboard (see
.env.examplefor the full list) - Add Redis: Railway dashboard → New → Database → Redis
- Deploy the remaining three agents:
railway link # link to the newly created project
./railway-deploy.sh --env-file .env qa dev notifierSee docs/architecture.md for a full breakdown.
Helix fixes application-level bugs only. Out of scope for MVP: infrastructure failures, performance optimisation, refactoring, mobile crashes, multi-tenant support.
After a user installs the GitHub App, GitHub is supposed to redirect to the configured callback URL with installation_id in the query string. This redirect is not reliably firing in the current deployment, which means the installation ID is never automatically saved to the project.
Workaround: after installing the app, find the installation ID in the GitHub App settings page (Settings → Applications → Installed GitHub Apps → Configure — the ID is in the URL: github.com/settings/installations/<id>) and paste it manually into the Helix Projects page using the manual entry field.
Root cause: the OAuth callback URL may not be correctly configured in the GitHub App settings, or the redirect is being swallowed before it reaches the handler.
Known limitations of the current architecture and what needs to change before running Helix as a multi-tenant SaaS.
Each agent is a single process that handles one incident at a time. At scale, incidents queue behind each other:
Org A crash → dev agent processing (5–10 min)
Org B crash → waiting...
Org C crash → waiting...
The Dev Agent is the worst offender — it clones a repo, runs tests, and iterates up to 3 times. A single slow incident blocks every other org.
Partial fix (Phase 6): Dev Agent now supports replicas via deploy.replicas in docker-compose. Redis Streams consumer groups distribute incidents across replicas automatically — no code changes required. Set replicas: 2 for up to 100 customers. For Railway, set the replica count in the service dashboard.
Remaining: auto-scaling based on queue depth (ECS Fargate or Railway replicas triggered by stream lag) is not yet implemented.
All orgs share the same worker pool. A single org with a burst of crashes can starve others.
Fix: priority queues per plan tier. Free incidents go into a low-priority queue; Pro into a standard queue; Team gets dedicated workers. Redis Streams supports this with multiple stream keys — workers poll the high-priority stream first.
| Plan | Model |
|---|---|
| Free | Shared pool, low-priority queue |
| Pro | Shared pool, standard-priority queue |
| Team | Dedicated worker pool — no noisy neighbour |
Before a TDD loop starts, the Dev Agent acquires SET helix:repo_lock:{repo} {incident_id} NX EX 600 in Redis. A second incident on the same repo retries every 30 seconds for up to 6 minutes; if the lock is still held it escalates to a human rather than forging ahead with a conflicting branch.
The TDD loop is now wrapped in asyncio.wait_for(..., timeout=480). If the 8-minute budget is exceeded the incident is escalated via the same path as exhausted retries — the human always gets a Slack alert. The lock TTL (600 s) is intentionally longer than the timeout so the lock always expires cleanly even if cancellation interrupts the Redis delete.
Helix now uses Redis Streams (XADD/XREAD) by default. Messages persist until consumed and survive agent restarts. Redis Pub/Sub is still available via redis_mode: pubsub in config.yaml for backwards compatibility.
config.yaml holds a single github.target_repo. There is no concept of per-org configuration, tokens, or repo lists.
Fix: replace config.yaml with a Postgres table (repos, organisations). Agents receive org_id in the event payload and look up config at runtime. Required before any multi-tenant work.
| Concern | Fix | Status |
|---|---|---|
| Sequential bottleneck (full auto-scale) | Queue-depth-triggered replica scaling | Pending |
| No org isolation / priority queues | Separate Redis Stream keys per plan tier | Pending |
deploy.replicas + consumer groups |
Done — Phase 6 | |
user_settings table + Settings page |
Done — Phase 6 | |
SET NX EX) |
Done — agents/dev/agent.py |
|
Done — asyncio.wait_for 8 min |
||
| Done — Redis Streams is the default | ||
| Done — Phase 3 Postgres layer |
None of these require changes to agent logic. The event-driven architecture is the right foundation — these are distribution and configuration concerns layered on top of it.
See docs/ROADMAP.md for the full phase-by-phase roadmap.
ollama pull Qwen3.6:latest
Verify it downloaded correctly:
ollama list
By default Ollama only binds to localhost, so remote machines cannot reach it. Create a systemd override file:
sudo mkdir -p /etc/systemd/system/ollama.service.d
sudo nano /etc/systemd/system/ollama.service.d/override.conf
Add exactly this content:
[Service]
Environment="OLLAMA_HOST=0.0.0.0"
Save and exit, then apply the change:
sudo systemctl daemon-reload
sudo systemctl restart ollama
Confirm it worked:
ss -tlnp | grep 11434
You should see 0.0.0.0:11434, not 127.0.0.1:11434.
sudo ufw allow 11434/tcp
To restrict to one specific machine only:
sudo ufw allow from 192.168.1.X to any port 11434
From another machine on the network:
curl http://192.168.1.9:11434/
curl http://192.168.1.9:11434/api/tags
Create or edit ~/.config/opencode/config.json with the following. Note that the key is provider (singular, not providers), and the base URL must use /v1 at the end since opencode uses the OpenAI-compatible API endpoint:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"ollama": {
"npm": "@ai-sdk/openai-compatible",
"name": "Ollama (local)",
"options": {
"baseURL": "http://localhost:11434/v1"
},
"models": {
"qwen3.6": {
"name": "qwen3.6"
},
"gemma4": {
"name": "Gemma 4"
}
}
}
}
}Add any other locally available models under models using the exact name that ollama list shows. Then run opencode using the provider and model name:
opencode run --dangerously-skip-permissions --model ollama/qwen3.6 "your prompt here"
If you do not want to maintain the config file, you can create a simpler alias for models with dots in their name:
ollama cp Qwen3.6:latest qwen36
This does not duplicate the model on disk. Then use:
opencode run --dangerously-skip-permissions --model ollama/qwen36 "your prompt here"
| Task | Command |
|---|---|
| Restart Ollama | sudo systemctl restart ollama |
| Check listening port | `ss -tlnp |
| Check firewall | sudo ufw status |
| List models | ollama list |
| Test remote connection | curl http://<ip>:11434/ |