Thanks for working on Moira. This is a single mono-repo for the product:
orchestrator/ Python sidecar — dependency-free DAG engine, gates, audit (hash chain),
pluggable persistence (SQLite/Postgres/git), HTTP API, pluggable backends
(mock / claude_code / litellm). Drives AI SDLC skills for discovery.
cockpit/ React + TypeScript + Vite frontend (the cockpit UI + mobile gate inbox).
src-tauri/ Tauri v2 desktop shell that spawns the Python sidecar.
docs/ Marketing landing pages and assets.
The AI SDLC framework content (intents, requirements, func-specs, ADRs, agents, skills) and any target application code live in separate repositories — Moira reads/writes them as a workspace, but they are not part of this product repo.
| Need | For |
|---|---|
| Python 3.11+ | orchestrator + API (stdlib only — no pip install needed) |
| Node 18+ / npm | building the cockpit |
claude CLI (logged in) |
the real agent/skill backend (optional — mock works offline) |
cargo + webkit2gtk |
the native desktop app (optional) |
./run-cockpit.sh # builds the cockpit, serves UI + API on http://127.0.0.1:8765
# dev mode (hot reload), two terminals:
python3 orchestrator/moira_api.py --repo /path/to/ai-sdlc-repo # API on :8765
npm --prefix cockpit run dev # UI on :5173 (proxies /api)
./run-desktop.sh # native desktop shell (needs cargo + webkit2gtk)
# optional external durable runner (team mode). External runners share ONE primary
# store across processes, so they REQUIRE Postgres — SQLite is rejected (unsafe
# multi-process). Disable the API's embedded runner, point both at Postgres:
export MOIRA_PRIMARY=postgres MOIRA_PG_DSN=postgresql://moira:moira@localhost:5432/moira
MOIRA_RUNNER_MODE=off python3 orchestrator/moira_api.py --repo /path/to/ai-sdlc-repo
python3 orchestrator/moira_runner.py --mode external # claims jobs from PostgresAuth:
run-cockpit.shdefaults toMOIRA_AUTH_MODE=local(the sidecar injects a session token into the served UI). Plainpython3 orchestrator/moira_api.pyandrun-desktop.shrunauth=off(no enforcement). For team mode setMOIRA_AUTH_MODE=oidc+MOIRA_OIDC_*andpip install "moira-orchestrator[auth]". Full env-var reference:orchestrator/PERSISTENCE.md.
python3 -m unittest discover -s orchestrator/tests -q # orchestrator (must stay green)
npm --prefix cockpit run build # type-check + production buildCI (GitHub Actions) runs both on every push/PR. The Tauri/Rust build is not in CI (it
needs webkit2gtk system deps); build it locally with cargo tauri build when touching src-tauri/.
Repo-reader resolution tests are opt-in: point MOIRA_CSL_REPO at a real AI SDLC repo to run
them, otherwise they skip.
Moira can drive a coding node with Claude Code Superpowers
(plan → TDD → systematic debugging → code review) instead of the custom dev@* skills — they
coexist, you opt in per run:
git clone https://github.com/obra/superpowers ~/.moira/plugins/superpowers
export MOIRA_SUPERPOWERS_DIR=~/.moira/plugins/superpowers # enables the opt-inThen pick the sdlc-superpowers pipeline (or the superpowers-coder agent in the editor).
The backend loads the plugin for that run only via claude --plugin-dir (role superpowers-coder),
so every other run — dev@*, discovery, eval, compliance — is untouched. With the env var unset,
behaviour is unchanged. Heavy coding roles get a larger turn/time budget automatically.
Releases are produced by .github/workflows/release.yml — push a tag v* (e.g. v0.1.0) and CI
builds installers for all platforms and publishes them (+ updater latest.json) to this repo's
Releases. In-app auto-update is enabled.
How the Python dependency ships: the orchestrator (zero-dep stdlib) is frozen with PyInstaller
into a single self-contained binary per OS/arch — with the built cockpit dist embedded — and bundled
as a Tauri externalBin sidecar. End users need no system Python. (Real agent backends still need
the claude CLI installed; mock works offline.) macOS ships per-arch (arm64 + Intel) because
PyInstaller can't cross-compile a universal binary.
Required GitHub secrets (Settings → Secrets and variables → Actions):
| Secret | Purpose |
|---|---|
TAURI_SIGNING_PRIVATE_KEY (+ _PASSWORD) |
signs updater artifacts (tauri signer generate; public key is in tauri.conf.json) |
APPLE_CERTIFICATE (+ _PASSWORD) |
Developer ID cert (.p12, base64) for macOS signing |
APPLE_SIGNING_IDENTITY, APPLE_TEAM_ID |
macOS code-signing identity |
APPLE_API_KEY, APPLE_API_ISSUER, APPLE_API_KEY_CONTENT |
App Store Connect API key for notarization |
Windows installers are currently unsigned (SmartScreen will warn) — add a code-signing cert later if needed.
Cut a release: bump version in src-tauri/tauri.conf.json (and optionally cockpit/package.json),
commit, then git tag vX.Y.Z && git push origin vX.Y.Z. Watch the Actions tab.
Runs launch non-blocking: POST /api/runs, /api/discovery, and gate approve/reject return a
run_id immediately and enqueue a durable runner job. In local/desktop mode the sidecar starts an
embedded runner thread, but the source of truth is the persisted job/lease state, not request-thread
memory. The cockpit then streams progress (Runs/Inbox poll every ~2.5 s), so a real claude step no
longer freezes the "Start"/decision button.
Where to look when something misbehaves:
- Live, per run: open Runs, select the run → execution plan (per-node status) + a streaming
activity log. Node failures show as
retry/node.escalateevents carrying the error (e.g.claudestderr or timeout). - Across runs: the Activity page → Events tab (all runs) and Sidecar logs tab (the orchestrator logfile, tailed live).
- Runner health:
GET /api/healthincludes runner mode, worker heartbeat, and job counts;GET /api/runnerreturns recent jobs and workers for durable-runner debugging. - Logfile on disk:
MOIRA_LOG(default next toMOIRA_DB→<app-data>/moira.login the desktop app; the path is also inGET /api/healthandGET /api/logs?tail=N). - Dev: run
./run-cockpit.sh(orpython3 orchestrator/moira_api.py …) in a terminal — the sidecar logs to stdout there too. For the UI, use the Tauri webview devtools.
Note: POST /api/eval (quality/conformance/compliance scorecards) is intentionally synchronous — it
returns the scorecard in the response, so that call does block until the judge finishes.
- Set
MOIRA_DEBUG=1(any value but0/false/empty) before launching the sidecar to make theclaude_codebackend record, as adebuglive record per node, the exact command + full prompt it hands the model (pluscwd, role and the chosen timeout), and the stderr/exit code on failure. No secrets are on the cmdline — theclaudeCLI authenticates from the keychain — so this is safe to keep. Whether it's on is shown inGET /api/health(config.debug). - Every run drill-down has a 🐞 Debug bundle button → downloads
moira-debug-<run_id>.json: run + pipeline + events + audit + cost + per-node state, the live stream (including the command/prompt above whenMOIRA_DEBUG=1), and the slice of the sidecar log for that run. Same payload asGET /api/runs/{id}/debug. Attach it to a bug report and the run is fully reproducible offline.
The claude_code backend runs in three budget tiers, all env-configurable (sane defaults). Skills fail
fast so a headless-broken ba@* escalates to a human gate in minutes instead of grinding; coding gets a
big budget. Current values are shown in GET /api/health (config).
| Env var | Default | Applies to |
|---|---|---|
MOIRA_CLAUDE_SKILL_TIMEOUT |
300 s |
discovery/authoring skill nodes (ba@*/pm@*/qa@*) |
MOIRA_CLAUDE_SKILL_MAX_TURNS |
20 |
skill nodes — enough turns for multi-file authoring (decompose/test-plan) |
MOIRA_SKILL_RETRIES |
1 |
skill retries before escalating (1 = 2 attempts) |
MOIRA_CLAUDE_TIMEOUT / MOIRA_CLAUDE_MAX_TURNS |
600 s / 12 |
default (analysts, verifiers, evals) |
MOIRA_CLAUDE_HEAVY_TIMEOUT / MOIRA_CLAUDE_HEAVY_MAX_TURNS |
1800 s / 40 |
coding (code-generator, superpowers-coder) |
So a broken skill escalates after skill_timeout × (skill_retries + 1) ≈ 10 min. The turn budget (20)
doesn't affect a hung skill — the watchdog kills on time — so raising turns keeps fail-fast intact while
letting healthy multi-file authoring skills finish. Set e.g. MOIRA_CLAUDE_SKILL_TIMEOUT=120 MOIRA_SKILL_RETRIES=0 for aggressive fail-fast, or bump MOIRA_CLAUDE_SKILL_MAX_TURNS for very large specs.
- Keep the orchestrator dependency-free (stdlib only) — it ships as the sidecar.
- Secrets stay in the OS keychain /
.env(gitignored) — never in the repo or logs. - Local state (
*.sqlite,.moira/,dist/,node_modules/,target/) is gitignored — don't commit it. - End commit messages with a
Co-Authored-By:trailer when pairing. - User-visible changes (features, behavior changes, fixes) get an entry in
CHANGELOG.md(Keep a Changelog format) in the same commit; architecture decisions get an ADR indocs/adr/.