Orcha AI extraction.
- Docker & Docker Compose
- uv (Python package manager)
- Python ≥ 3.14
uv syncuv run orcha services startuv run orcha migrate# Start both server and worker
uv run orcha run
# Or start them individually
uv run orcha run server # FastAPI dev server
uv run orcha run workers # Temporal worker for the default queue
# Run a worker for a specific queue
uv run orcha run workers --task-queue low-priorityThe API uses multi-tenant RS256 (asymmetric) JWT authentication. Each tenant has its own RSA key pair(s). The tenant signs tokens with their private key; the server verifies them using the tenant's registered public key.
Tenants are identified by the iss (issuer) claim in the JWT. To support zero-downtime key rotation, the server allows multiple public keys per tenant. In token headers, tenants must include a Key ID (kid) that matches one of their defined keys in the configuration.
Create a tenants.json file at the project root:
{
"tenant-a": {
"name": "Tenant A",
"public_keys": {
"kid-1": "-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----"
}
},
"tenant-b": {
"name": "Tenant B",
"public_keys": {
"kid-1": "-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----"
}
}
}Each key in the JSON must match the iss claim the tenant will use in their JWTs.
⚠️ Never committenants.jsonor.pemfiles — they are already in.gitignore.
Each tenant generates their own key pair and sends you only the public key:
# Generate a 2048-bit RSA private key (tenant keeps this secret)
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
# Extract the public key (send this to the server operator)
openssl rsa -pubout -in private_key.pem -out public_key.pem| Variable | Description | Required |
|---|---|---|
JWT_ALGORITHM |
Signing algorithm (default: RS256) | No |
AUTH_DISABLED |
Set to true to skip auth |
Development |
TENANTS_CONFIG_PATH |
Path to tenants JSON (default: tenants.json) | Production |
Local development — bypass authentication entirely:
export AUTH_DISABLED=trueTokens must include the iss claim matching the tenant ID. Optionally include workflow_id to scope access.
import jwt
from datetime import datetime, timedelta, timezone
private_key = open("private_key.pem").read()
token = jwt.encode(
{
"iss": "tenant-a", # Required: must match tenants.json key
"workflow_id": "YOUR_WORKFLOW_ID", # Optional: scope to a specific workflow
"exp": datetime.now(timezone.utc) + timedelta(hours=1)
},
private_key,
algorithm="RS256",
headers={"kid": "kid-1"} # Required: must match kid in tenants.json public_keys
)
print(token)Use the token:
curl -H "Authorization: Bearer <token>" http://localhost:8000/workflows/<YOUR_WORKFLOW_ID>The service supports multiple LLM backends. Configure a single setting, LLM,
in the form <provider>/<model>.
export LLM="litellm/groq/qwen/qwen3-32b"Optional configuration:
export LITELLM_API_BASE="<litellm-endpoint>"
export LITELLM_API_KEY="<api-key>"export LLM="ollama/llama3.1"
export OLLAMA_BASE_URL="http://localhost:11434/v1"| Command | Description |
|---|---|
orcha services start |
Start PostgreSQL + Temporal via Docker |
orcha services stop |
Stop all Docker services |
orcha migrate |
Apply all database migrations |
orcha run |
Start server and default-queue worker |
orcha run server |
Start FastAPI dev server only |
orcha run workers |
Start Temporal worker for default queue |
orcha run workers --task-queue Q |
Start Temporal worker for a specific queue |
The deployed schema is managed with Alembic. Apply committed migrations with:
uv run alembic upgrade headFor local setup, uv run orcha migrate is a convenience wrapper around the
same Alembic upgrade.
See Database Migrations for the full process of generating and reviewing migrations when SQLModel models change.
# Stop and remove volumes (reset databases)
docker compose down -v
# View Docker service logs
docker compose logs -f
# Open Temporal UI
open http://localhost:8080Release for Orcha are done manually. Pushing a v* tag triggers the image build, so commit the version bump and changelog before you tag.
Bump pyproject.toml and re-lock in one step:
uv version --bump patch # or: minor, majorThen set the same X.Y.Z in charts/orcha/Chart.yaml, in both version and appVersion.
CHANGELOG.md follows Keep a Changelog. Every feat:/fix:/refactor: commit should already have a bullet under ## [Unreleased]. At release time, promote that section:
- Rename
## [Unreleased]to## [X.Y.Z] - YYYY-MM-DDand open a fresh empty## [Unreleased]above it. - In the link footer, repoint
[unreleased]tovX.Y.Z...HEADand add[X.Y.Z]: .../compare/<prev>...vX.Y.Z.
git commit -am "release: vX.Y.Z"
git tag vX.Y.Z
git push origin main vX.Y.ZThe tag push kicks off the Docker workflow below, which builds and publishes the image.
Docker images are automatically built and published to registry.cern.ch/orcha/orcha by the Docker workflow.
| Event | Image tag |
|---|---|
Push a v* tag (e.g. v1.2.3) |
1.2.3 |
Manual via GitHub UI (workflow_dispatch) |
depends on branch/tag |
The workflow uses two repository secrets that must be configured in Settings → Secrets and variables → Actions:
| Secret | Description |
|---|---|
REGISTRY_USER |
registry robot account username |
REGISTRY_PASSWORD |
registry robot account password |