quorum-orchestrator 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. quorum_orchestrator-0.1.0/.github/workflows/ci.yml +59 -0
  2. quorum_orchestrator-0.1.0/.github/workflows/release.yml +37 -0
  3. quorum_orchestrator-0.1.0/.gitignore +9 -0
  4. quorum_orchestrator-0.1.0/.python-version +1 -0
  5. quorum_orchestrator-0.1.0/CLAUDE.md +224 -0
  6. quorum_orchestrator-0.1.0/PKG-INFO +200 -0
  7. quorum_orchestrator-0.1.0/README.md +181 -0
  8. quorum_orchestrator-0.1.0/docs/architecture.md +406 -0
  9. quorum_orchestrator-0.1.0/docs/guide.md +667 -0
  10. quorum_orchestrator-0.1.0/docs/images/tui.png +0 -0
  11. quorum_orchestrator-0.1.0/docs/images/web-dark.png +0 -0
  12. quorum_orchestrator-0.1.0/docs/images/web-light.png +0 -0
  13. quorum_orchestrator-0.1.0/examples/steward.py +238 -0
  14. quorum_orchestrator-0.1.0/integrations/claude-code/.claude-plugin/plugin.json +5 -0
  15. quorum_orchestrator-0.1.0/integrations/claude-code/README.md +74 -0
  16. quorum_orchestrator-0.1.0/integrations/claude-code/commands/adopt.md +19 -0
  17. quorum_orchestrator-0.1.0/integrations/claude-code/hooks/hooks.json +24 -0
  18. quorum_orchestrator-0.1.0/integrations/codex/README.md +90 -0
  19. quorum_orchestrator-0.1.0/integrations/codex/hooks.json +13 -0
  20. quorum_orchestrator-0.1.0/integrations/codex/prompts/quorum-adopt.md +20 -0
  21. quorum_orchestrator-0.1.0/integrations/opencode/README.md +72 -0
  22. quorum_orchestrator-0.1.0/integrations/opencode/commands/quorum-adopt.md +18 -0
  23. quorum_orchestrator-0.1.0/integrations/opencode/plugin/quorum.js +108 -0
  24. quorum_orchestrator-0.1.0/pyproject.toml +58 -0
  25. quorum_orchestrator-0.1.0/src/quorum/__init__.py +6 -0
  26. quorum_orchestrator-0.1.0/src/quorum/__main__.py +10 -0
  27. quorum_orchestrator-0.1.0/src/quorum/actor.py +57 -0
  28. quorum_orchestrator-0.1.0/src/quorum/agent.py +131 -0
  29. quorum_orchestrator-0.1.0/src/quorum/agents/__init__.py +24 -0
  30. quorum_orchestrator-0.1.0/src/quorum/agents/harness_run.py +89 -0
  31. quorum_orchestrator-0.1.0/src/quorum/agents/manager.py +189 -0
  32. quorum_orchestrator-0.1.0/src/quorum/agents/prompt_agent.py +42 -0
  33. quorum_orchestrator-0.1.0/src/quorum/cli.py +1498 -0
  34. quorum_orchestrator-0.1.0/src/quorum/config.py +266 -0
  35. quorum_orchestrator-0.1.0/src/quorum/default_prompts/manager.md +55 -0
  36. quorum_orchestrator-0.1.0/src/quorum/default_prompts/task-preamble.md +37 -0
  37. quorum_orchestrator-0.1.0/src/quorum/fsio.py +259 -0
  38. quorum_orchestrator-0.1.0/src/quorum/herdr.py +91 -0
  39. quorum_orchestrator-0.1.0/src/quorum/home.py +171 -0
  40. quorum_orchestrator-0.1.0/src/quorum/llm/__init__.py +91 -0
  41. quorum_orchestrator-0.1.0/src/quorum/llm/cli_backend.py +57 -0
  42. quorum_orchestrator-0.1.0/src/quorum/llm/proxy_backend.py +24 -0
  43. quorum_orchestrator-0.1.0/src/quorum/messages.py +263 -0
  44. quorum_orchestrator-0.1.0/src/quorum/projects.py +176 -0
  45. quorum_orchestrator-0.1.0/src/quorum/prompts.py +37 -0
  46. quorum_orchestrator-0.1.0/src/quorum/registry.py +55 -0
  47. quorum_orchestrator-0.1.0/src/quorum/runner.py +420 -0
  48. quorum_orchestrator-0.1.0/src/quorum/sandbox.py +327 -0
  49. quorum_orchestrator-0.1.0/src/quorum/supervisor.py +377 -0
  50. quorum_orchestrator-0.1.0/src/quorum/tasks.py +362 -0
  51. quorum_orchestrator-0.1.0/src/quorum/tui/__init__.py +0 -0
  52. quorum_orchestrator-0.1.0/src/quorum/tui/app.py +250 -0
  53. quorum_orchestrator-0.1.0/src/quorum/views.py +262 -0
  54. quorum_orchestrator-0.1.0/src/quorum/web/__init__.py +0 -0
  55. quorum_orchestrator-0.1.0/src/quorum/web/app.py +153 -0
  56. quorum_orchestrator-0.1.0/src/quorum/web/static/index.html +375 -0
  57. quorum_orchestrator-0.1.0/tests/bin/fake_harness.py +146 -0
  58. quorum_orchestrator-0.1.0/tests/bin/fake_llm.py +32 -0
  59. quorum_orchestrator-0.1.0/tests/bin/opencode_plugin_driver.mjs +53 -0
  60. quorum_orchestrator-0.1.0/tests/conftest.py +56 -0
  61. quorum_orchestrator-0.1.0/tests/test_adopt.py +234 -0
  62. quorum_orchestrator-0.1.0/tests/test_cli.py +538 -0
  63. quorum_orchestrator-0.1.0/tests/test_example_steward.py +123 -0
  64. quorum_orchestrator-0.1.0/tests/test_fsio.py +100 -0
  65. quorum_orchestrator-0.1.0/tests/test_harness_integration.py +139 -0
  66. quorum_orchestrator-0.1.0/tests/test_herdr.py +172 -0
  67. quorum_orchestrator-0.1.0/tests/test_integrations.py +227 -0
  68. quorum_orchestrator-0.1.0/tests/test_llm.py +85 -0
  69. quorum_orchestrator-0.1.0/tests/test_manager.py +223 -0
  70. quorum_orchestrator-0.1.0/tests/test_messages.py +117 -0
  71. quorum_orchestrator-0.1.0/tests/test_nono_integration.py +187 -0
  72. quorum_orchestrator-0.1.0/tests/test_projects.py +67 -0
  73. quorum_orchestrator-0.1.0/tests/test_prompt_agent.py +143 -0
  74. quorum_orchestrator-0.1.0/tests/test_sandbox.py +309 -0
  75. quorum_orchestrator-0.1.0/tests/test_supervisor.py +320 -0
  76. quorum_orchestrator-0.1.0/tests/test_tasks.py +308 -0
  77. quorum_orchestrator-0.1.0/tests/test_tui.py +110 -0
  78. quorum_orchestrator-0.1.0/tests/test_web.py +111 -0
  79. quorum_orchestrator-0.1.0/uv.lock +759 -0
@@ -0,0 +1,59 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v5
14
+ with:
15
+ enable-cache: true
16
+ - run: uv sync --locked
17
+ - run: uv run ruff check .
18
+
19
+ test:
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ python-version: ["3.11", "3.12", "3.13"]
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: astral-sh/setup-uv@v5
28
+ with:
29
+ enable-cache: true
30
+ python-version: ${{ matrix.python-version }}
31
+ - run: uv sync --locked --all-extras
32
+ # Unit tests only; test_sandbox.py covers the nono glue with a fake
33
+ # nono_py (fail-closed behavior, capability derivation, exec mapping).
34
+ - run: uv run pytest -m "not nono_integration"
35
+
36
+ # Real sandbox enforcement: installs the actual nono-py wheel and verifies
37
+ # the kernel enforces the capability set quorum derives — allowed writes
38
+ # succeed, out-of-capability writes fail, project dirs are read-only, and
39
+ # Mode 2/3 work end-to-end. GitHub's ubuntu runners have Landlock enabled,
40
+ # and the support gate below fails loudly if that ever changes, so these
41
+ # tests can never silently self-skip in CI.
42
+ nono-integration:
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+ - uses: astral-sh/setup-uv@v5
47
+ with:
48
+ enable-cache: true
49
+ - run: uv sync --locked --extra nono
50
+ - name: Assert sandbox support on this runner
51
+ run: |
52
+ uv run python - <<'EOF'
53
+ import sys
54
+ import nono_py
55
+ info = nono_py.support_info()
56
+ print(info)
57
+ sys.exit(0 if info.is_supported else 1)
58
+ EOF
59
+ - run: uv run pytest -m nono_integration -v -ra
@@ -0,0 +1,37 @@
1
+ name: Release
2
+
3
+ # Publishes to PyPI when a version tag is pushed. Auth is PyPI "trusted
4
+ # publishing" (OIDC): the quorum-orchestrator project on PyPI trusts this
5
+ # repo + workflow + environment, so no API token is stored anywhere.
6
+ on:
7
+ push:
8
+ tags: ["v*"]
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: astral-sh/setup-uv@v5
16
+ with:
17
+ enable-cache: true
18
+ - run: uv sync --locked
19
+ - run: uv run pytest -m "not nono_integration"
20
+ - run: uv build
21
+ - uses: actions/upload-artifact@v4
22
+ with:
23
+ name: dist
24
+ path: dist/
25
+
26
+ publish:
27
+ needs: build
28
+ runs-on: ubuntu-latest
29
+ environment: pypi
30
+ permissions:
31
+ id-token: write # required for trusted publishing
32
+ steps:
33
+ - uses: actions/download-artifact@v4
34
+ with:
35
+ name: dist
36
+ path: dist/
37
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ quorum-home/
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,224 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ uv sync --all-extras # dev setup (extras: web, nono; the TUI is a core dep)
9
+ uv run pytest # full suite
10
+ uv run pytest tests/test_tasks.py::test_run_creates_worktree_and_streams_transcript
11
+ uv run pytest -m "not nono_integration" # what CI's unit-test matrix runs
12
+ uv run pytest -m nono_integration -v # real kernel sandbox tests (need [nono] + Landlock/Seatbelt)
13
+ QUORUM_HARNESS_TESTS=1 uv run pytest -m "codex_integration or opencode_integration" -v
14
+ # real codex/opencode adoption tests (binaries + auth; spend tokens)
15
+ uv run ruff check . # lint (line-length 100; E4,E7,E9,F,I,UP,B)
16
+ uv run quorum <cmd> # run the CLI from a checkout
17
+ ```
18
+
19
+ The PyPI distribution is `quorum-orchestrator` (plain `quorum` was taken); the
20
+ import name and CLI command stay `quorum`. Releases: bump `version` in
21
+ pyproject.toml, tag `vX.Y.Z`, push the tag — `.github/workflows/release.yml`
22
+ builds with uv and publishes to PyPI via trusted publishing (OIDC, no token).
23
+
24
+ `tests/test_nono_integration.py` self-skips when nono-py is missing or the platform
25
+ lacks Landlock/Seatbelt; a dedicated CI job asserts support so it can never silently
26
+ skip there. `test_web.py` needs the `web` extra (it `importorskip`s FastAPI).
27
+
28
+ ## What quorum is
29
+
30
+ Bring-your-own-harness orchestration for long-running coding tasks: the user
31
+ registers projects, queues tasks in plain English, and a configured harness CLI
32
+ (claude / codex / opencode / anything) executes each task as a sequence of *runs*
33
+ in a per-task git worktree. Supervision is **itself harness-driven**: the one
34
+ built-in agent (the manager) runs the same harness over a situation digest and
35
+ acts through the quorum CLI. Quorum ships the manager and the comms substrate
36
+ (file-based board + inboxes), **not** prescriptive worker agents, and supervision
37
+ policy lives in `prompts/manager.md`, not Python.
38
+
39
+ Read `docs/architecture.md` first — it is the design record. The three invariants
40
+ below govern nearly every change:
41
+
42
+ 1. **No privileged infrastructure.** `quorum up` is one ordinary process hosting an
43
+ APScheduler `BackgroundScheduler` — foreground by default, or detached with
44
+ `up --detach` the same way task runs detach (`quorum down` SIGTERMs it and polls
45
+ the lock). Task runs are detached child processes (they survive supervisor
46
+ restarts). No cron, systemd, daemonization frameworks, root, or open ports (the
47
+ web dashboard is opt-in, localhost-only).
48
+ 2. **All state is plain files** under `QUORUM_HOME` (resolution: `--home` > `$QUORUM_HOME`
49
+ > `./quorum-home` if present > `~/.quorum`). No database. Adding new durable state
50
+ means adding a file layout, documented in `docs/architecture.md`.
51
+ 3. **Fail loudly, recover automatically.** Views/dashboards degrade gracefully (pure
52
+ file readers; work with the supervisor stopped) and a harness that ignores the
53
+ report protocol is still observed passively — but supervision has **no no-LLM
54
+ fallback by design**: without a working harness the manager's tick raises every
55
+ time, and its `auto_pause = false` config keeps the schedule firing so it
56
+ self-recovers when the LLM service returns. Do not add degraded supervision paths.
57
+
58
+ These are the project's *current* design commitments, not gospel. Quorum is
59
+ evolving: any recorded stance — including the big three above and smaller ones
60
+ noted per-layer below (e.g. the TUI/web being pure readers) — is open to
61
+ deliberate revision when a change is worth it. Don't contort a feature to fit
62
+ an old rule; propose breaking the rule, and when it changes, update this file
63
+ and `docs/architecture.md` in the same commit so the record stays true.
64
+
65
+ ### Layers
66
+
67
+ - `fsio.py` — the primitives everything else stands on: `atomic_write_*` (dot-prefixed
68
+ tmp in the same dir + fsync + rename), `append_jsonl`, ULID generation, `sorted_entries`
69
+ (skips dotfiles), and a pid-lock built on `O_EXCL` rather than `flock` so it behaves
70
+ identically under any sandbox-granted filesystem. **Never write state with plain
71
+ `open(...,'w')`** — readers must never observe a partial file.
72
+ - `messages.py` — one `Message` schema over two channels: an append-only board
73
+ (`messages/board/<topic>/`, filenames `<utc-compact>-<ULID>.json` so lexicographic
74
+ order is chronological) and maildir-style inboxes (`new/` → `cur/` claimed by
75
+ `os.rename`, so exactly one claimant wins). Task guidance (`task-<id>` inboxes) and
76
+ the supervisor control channel both ride this; no new transports.
77
+ - `tasks.py` — the task substrate: `Task`/`TaskStore` over `tasks/<id>/task.json`,
78
+ `report()` (the harness's return channel), path helpers shared by runner/manager/
79
+ views/CLI. **Status is a free-form reported string**; only `TERMINAL_STATUSES`
80
+ (`done`/`blocked`/`cancelled`) mean anything to quorum. `short_id` is the ULID's
81
+ random *tail* (the head is a same-instant-shared timestamp); `resolve()` accepts
82
+ unique prefixes or suffixes. `workdir_git_state` is the stranded-work probe
83
+ (dirty/unpushed in a task's workdir) surfaced by views and the manager digest —
84
+ the preamble tells harnesses to commit+push with plain git before reporting done.
85
+ `attached = true` marks an *adopted* live interactive session (`quorum task
86
+ adopt`): workdir = the user's checkout, no worktree, liveness from
87
+ `tasks/<id>/attached.json` (rewritten by `task hook-session-start`/
88
+ `hook-stop`/`hook-session-end`, which also learn the session id by cwd
89
+ match and deliver pending inbox guidance — as the Stop-hook block-protocol
90
+ JSON, or bare text via `hook-stop --format text` for shims that inject the
91
+ continuation themselves); `task detach` reverts it. One adapter per
92
+ harness under `integrations/` (claude-code, codex, opencode — the last is
93
+ a fail-soft JS plugin, kept a dumb pipe over the same CLI entry points),
94
+ kept true by `tests/test_integrations.py` (the opencode plugin is driven
95
+ for real under node, skipped when node is absent). The adapters ship
96
+ inside the wheel (hatch force-include → `quorum/integrations`) so
97
+ `quorum integration list|install` works from a package install;
98
+ `cli._integrations_root()` falls back to the repo dir in a checkout.
99
+ - `runner.py` — one harness run: `runner.lock` pid-lock → git worktree under
100
+ `worktrees/<id>` (branch `quorum/<short-id>`) → claim task inbox → compose prompt
101
+ (preamble + task + guidance) → substitute `{prompt}`/`{session}` into the
102
+ `[harness.<name>]` argv template → stream stdout to `transcript.jsonl`, capturing
103
+ `session_id`/`thread_id`. A harness with `inject = "stream-json"` also gets
104
+ mid-run guidance: `GuidancePump` holds stdin open, forwards inbox messages as
105
+ stream-json user turns, and closes stdin at the first idle `result` event (the
106
+ manager reuses the pump over the `manager` inbox). The runner **never sets task
107
+ status**, and refuses attached tasks outright — a substrate rail (same class as
108
+ `runner.lock`, a deliberate narrow bend of "the cap is the only rail") protecting
109
+ the user's live checkout. `launch_detached` spawns `python -m quorum task run`
110
+ in a new session.
111
+ - `agents/manager.py` — the flagship builtin, and it makes **no decisions in Python**:
112
+ its tick builds a situation digest (`build_digest`, pure over files — task
113
+ statuses, runner liveness, quiet time, report/transcript tails, the manager's own
114
+ action journal with then-vs-now outcomes, user directives from the `manager`
115
+ inbox), renders `prompts/manager.md`, and runs the configured harness
116
+ synchronously (cwd=home, tagged with the `actor.py` env protocol —
117
+ `QUORUM_ACTOR=manager`, per-run `QUORUM_ACTOR_RUN`, the resolved cap in
118
+ `QUORUM_ACTOR_CAP` — bounded by `run_timeout_seconds`, stdout →
119
+ `state/manager/transcript.jsonl`).
120
+ The harness acts via the quorum CLI; the CLI's `_actor_guard` auto-journals
121
+ every mutating action to the acting agent's journal and enforces the per-run
122
+ action cap (`max_actions_per_run`) — the only rail, a rate limit, never a veto.
123
+ Failures raise; directives are rejected back to `new/` on crash.
124
+ `agents/harness_run.py` holds the extracted run mechanics (`run_agent_harness`)
125
+ shared with `agents/prompt_agent.py::PromptAgent` (builtin `prompt`) — the
126
+ generic sibling: renders `prompts/<name>.md` (no digest, no wake condition;
127
+ conditional behavior belongs in the prompt) and runs the harness with the
128
+ same journal/cap rails under `state/agents/<name>/`. Prompt agents are
129
+ usually file-defined and created by `quorum agent create` or the web form.
130
+ - `agent.py` — `Agent` (synchronous, idempotent `tick()`) plus `AgentContext`, the single
131
+ seam through which agents touch the world: `ctx.bus`, `ctx.projects`, `ctx.llm`,
132
+ `ctx.prompt()`, `ctx.load_state()/save_state()`, `ctx.log_action()`, `ctx.now()`.
133
+ Agents take a clock as a callable — use `ctx.now()`, never `datetime.now()`.
134
+ `tick_lock_path` is held by both the supervisor wrapper and `agent run-once`.
135
+ - `supervisor.py` — one scheduler job per enabled agent, wrapped by `run_agent_tick`
136
+ for crash isolation: heartbeat files, an `agent.error` post to the `system` topic,
137
+ auto-pause after `MAX_CONSECUTIVE_FAILURES` (5). A 15s `_control` job claims the
138
+ `supervisor` inbox (`quorum agent pause|resume|run-now|reload`); `agent.reload`
139
+ is the hot-add/edit/remove path for file-defined agents (re-reads config, one
140
+ message for all mutations — handled *before* the job-exists guard). Pause is
141
+ durable: `_schedule_agent` creates the job paused when the heartbeat says
142
+ `paused`. An hourly janitor archives expired board messages and returns
143
+ crash-orphaned `cur/` claims to `new/`.
144
+ - `views.py` — the shared read-model assembled purely from files (`overview` includes
145
+ `attention_summary`, a time-windowed read of the `attention` topic that `status`,
146
+ the TUI banner, and the web header all surface — the board has no read-state, so
147
+ "needs a look" is time-bounded, not tracked); `quorum status`, the
148
+ web app, and the TUI are all pure readers of it. `agent_rows` estimates a stale
149
+ `next_run` from the schedule (`next_run_estimated`); `agent_detail` adds journal +
150
+ per-agent actions. Write affordances stay thin bus/config calls shared with the
151
+ CLI: nudging a task (TUI+web), and in the web only, board posts, project edits,
152
+ agent create (via `config.create_agent`) and pause/resume/run-now/reload.
153
+ - `actor.py` — the actor-identity env protocol: who a quorum CLI call is acting
154
+ as, name-generic over harness-driven agents. An agent tags the harness it
155
+ spawns (`actor_env(name, run_id, cap)`), the CLI resolves `current_actor()`
156
+ for journaling and message attribution, and the runner `strip_actor_env`s
157
+ spawned children so they act as themselves. Also owns `journal_path`/
158
+ `transcript_path` (manager at `state/manager/`, others at `state/agents/<name>/`).
159
+ - `registry.py` — resolves an agent `type` string: builtin short name (`manager`,
160
+ `prompt`), else `module:Class` with `QUORUM_HOME/plugins` prepended to `sys.path`.
161
+ - `llm/` — `LLMBackend` is a one-method protocol for *plugin agents'* small
162
+ completions — neither task harnesses nor the manager go through it. `LLMClient.complete()`
163
+ **never raises**; `None` means "no LLM today". No module outside `llm/` may assume
164
+ the `cli` backend (`proxy` is a reserved seam).
165
+ - `sandbox.py` — the *only* module that imports `nono_py`, always lazily and inside
166
+ functions. It **fails closed**. `build_capabilities` (supervisor/LLM) blocks network
167
+ unless `[llm]` is set; `build_task_capabilities` (per-run) grants the worktree, the
168
+ project's `.git` (shared object store), and `[sandbox].task_read/task_write` extras,
169
+ with network open.
170
+ - `herdr.py` — the *only* module that talks to a herdr server (terminal
171
+ multiplexer with agent-aware panes), over its unix-socket newline-JSON API.
172
+ **Fails soft** — the deliberate opposite of sandbox.py's fail-closed: herdr
173
+ absent/broken degrades every call to `None`/`False`, never breaking a digest
174
+ or nudge. Two narrow uses, both for attached tasks with a `herdr_pane`:
175
+ `agent_state` (pane status into the digest) and `ring_doorbell` (a
176
+ `task nudge` pokes the pane that guidance is waiting — the payload stays in
177
+ the maildir inbox; herdr is a doorbell, never a second transport). Optional
178
+ `[herdr]` table (`socket` override, `enabled`).
179
+ - `config.py` — `config.toml` is user-owned and **quorum never writes it back**; machine
180
+ state goes to JSON. The one config location quorum may write is `agents/<name>.toml`
181
+ (file-defined agents, atomic whole-file writes via `write_agent_file`/`create_agent`;
182
+ merged over `[agents.*]` at load, file wins; names validated + reserved-checked).
183
+ `[harness.<name>]` tables are argv templates; `[tasks]` holds
184
+ worktree/default-harness; `AgentConfig.auto_pause=false` exempts an agent from the
185
+ 5-failure auto-pause (the manager uses it). Schedules are validated by regex and
186
+ translated to APScheduler trigger kwargs by `parse_schedule`.
187
+ - `projects.py` — `projects/<slug>.json` is canonical, but a `.quorum.toml` marker inside
188
+ the project directory merges over it at read time. Agents and views must go through
189
+ `ProjectRegistry` and must only ever *read* project dirs — task writes happen in
190
+ worktrees.
191
+ - `prompts.py` — `QUORUM_HOME/prompts/<name>.md` overrides the packaged
192
+ `default_prompts/` (`task-preamble`, `manager`); deleting a file restores the
193
+ default. `format_map` with a missing-key-preserving dict. Re-running `quorum
194
+ init` upgrades seeded-but-never-edited copies, recognized by hash — **when you
195
+ change a file in `default_prompts/`, append the replaced version's sha256 to
196
+ `home.py::SUPERSEDED_PROMPT_HASHES`** (`git show HEAD:src/quorum/default_prompts/<name> | shasum -a 256`).
197
+ - `examples/steward.py` — the one shipped example plugin (file organizer with undo),
198
+ loaded by path in `tests/test_example_steward.py` so the docs' worked example stays
199
+ true. Not a builtin; users copy it into `plugins/`.
200
+
201
+ ### Adding an agent
202
+
203
+ Builtins live in `agents/__init__.py::BUILTIN_NAMES` (`manager`, `prompt`) —
204
+ prefer plugins unless quorum itself needs the behavior. The user-facing contract is
205
+ `docs/guide.md#writing-your-own-agents`: idempotent `tick()`, dedupe repeat
206
+ announcements through `load_state()/save_state()`, raising is safe.
207
+
208
+ ### Testing idioms
209
+
210
+ `tests/conftest.py` provides `home` (scaffolded `QUORUM_HOME` in `tmp_path`, exported via
211
+ `$QUORUM_HOME`), `clock` (a `FakeClock` passed as `AgentContext(now=...)`), and `fake_llm`.
212
+ `tests/bin/fake_harness.py` is a fake coding harness (echoes argv/prompt, emits a
213
+ `session_id`; `report` mode calls `python -m quorum task report`; `manager_act` /
214
+ `manager_flood` modes act like a manager — each `[harness.*]` table pins its mode via
215
+ its `env` field, so a fake task harness and a fake manager harness coexist). Runner and
216
+ manager tests build real git repos and run the loop for real; when a test needs a
217
+ "live" runner, its lock holds pid 1 — never `os.getpid()`, which same-process lock
218
+ takeover treats as stale.
219
+ `test_sandbox.py` injects a fake `nono_py` via `sys.modules`; `test_nono_integration.py`
220
+ exercises real kernel enforcement.
221
+
222
+ Docs are part of the deliverable here: a change to the file layout, message protocol,
223
+ task/run lifecycle, or sandbox modes should update `docs/architecture.md` (and the
224
+ user-facing `docs/guide.md`) in the same commit.
@@ -0,0 +1,200 @@
1
+ Metadata-Version: 2.5
2
+ Name: quorum-orchestrator
3
+ Version: 0.1.0
4
+ Summary: Bring-your-own-harness orchestration for long-running coding tasks: file-based, sandboxable, monitored.
5
+ Author: Kevin Doherty
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: apscheduler<4,>=3.10
9
+ Requires-Dist: pydantic>=2
10
+ Requires-Dist: textual>=0.60
11
+ Requires-Dist: typer>=0.12
12
+ Provides-Extra: nono
13
+ Requires-Dist: nono-py; extra == 'nono'
14
+ Provides-Extra: tui
15
+ Provides-Extra: web
16
+ Requires-Dist: fastapi>=0.110; extra == 'web'
17
+ Requires-Dist: uvicorn>=0.29; extra == 'web'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # quorum
21
+
22
+ **Orchestrate long-running coding tasks with the harness you already use —
23
+ supervised by that same harness.** Point quorum at your repos, queue tasks
24
+ in plain English, and let your coding agent CLI — `claude`, `codex`,
25
+ `opencode`, anything that takes a prompt — do the work in isolated git
26
+ worktrees. The supervisor is an agent too: quorum's **manager** periodically
27
+ hands your harness a digest of everything happening (every task's status,
28
+ output, and liveness, plus its own past actions and their outcomes) and lets
29
+ it decide — launch, nudge, relaunch, spin up follow-up work, or escalate to
30
+ you. Supervision policy is a prompt you can edit, not code. One process, no
31
+ root, no cron, no database; everything is a plain file you can `cat`.
32
+
33
+ ```
34
+ quorum task add my-api "add rate limiting to the public endpoints, then open a PR"
35
+ │
36
+ ┌─ quorum up ─────────────────────────────────────────────────────┐
37
+ │ manager your harness, reading the whole situation and │
38
+ │ acting: launch, nudge, relaunch, task add, │
39
+ │ escalate — every action journaled and auditable │
40
+ │ │
41
+ │ task runs your harness (claude/codex/opencode/...) working │
42
+ │ in worktrees/<task>/, reporting progress back │
43
+ │ through `quorum task report` and reading guidance │
44
+ │ from `quorum task inbox` │
45
+ └──────────────────── all state in ~/.quorum ─────────────────────┘
46
+ ▲ ▲ ▲
47
+ quorum status quorum tui quorum web
48
+ quorum manager tell (steer with `n`) (localhost only)
49
+ ```
50
+
51
+ ## Install
52
+
53
+ Needs Python 3.11+.
54
+
55
+ ```bash
56
+ uv tool install quorum-orchestrator # includes the TUI dashboard
57
+ uv tool install "quorum-orchestrator[web]" # + the localhost web dashboard
58
+ uvx --from quorum-orchestrator quorum --help # zero-install trial run
59
+ ```
60
+
61
+ (or `pip install "quorum-orchestrator[web]"` if you don't use uv.)
62
+
63
+ The PyPI distribution is `quorum-orchestrator`; the command it installs is
64
+ plain `quorum` (and the import name is `quorum` too).
65
+
66
+ From a checkout: `uv sync --all-extras`, then prefix commands with `uv run`.
67
+
68
+ ## Five minutes to a running task
69
+
70
+ ```bash
71
+ quorum init # scaffold ~/.quorum
72
+ ```
73
+
74
+ Tell quorum how to invoke your harness: the scaffolded
75
+ `~/.quorum/config.toml` ships ready-to-uncomment blocks for claude, codex,
76
+ and opencode, plus a template for any custom agentic binary — uncomment one
77
+ and set `default_harness`. (Runs are unattended, so the harness needs
78
+ permission to act without asking; the shipped blocks use a scoped tool
79
+ allowlist, not a blanket bypass.)
80
+
81
+ Register a repo and queue work:
82
+
83
+ ```bash
84
+ quorum doctor # verify the setup end to end
85
+ quorum project add ~/work/my-api
86
+ quorum task add my-api "fix the flaky auth tests and open a PR"
87
+ quorum up --detach # supervisor in the background (`quorum down` stops it)
88
+ ```
89
+
90
+ (`quorum up` without `--detach` runs it in the foreground — Ctrl-C stops.)
91
+ Then:
92
+
93
+ ```bash
94
+ quorum status # supervisor, agents, tasks, deadlines
95
+ quorum task tail a3f2k9 -f # live harness transcript
96
+ quorum task nudge a3f2k9 "prefer the retry approach over sleeps"
97
+ quorum manager tell "the api task is urgent; park everything else"
98
+ quorum manager journal # what the manager did, and why
99
+ quorum tui # dashboard; select a task, press n to steer
100
+ quorum web # http://127.0.0.1:8787
101
+ ```
102
+
103
+ ## What it looks like
104
+
105
+ <picture>
106
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/kvndhrty/quorum/main/docs/images/web-dark.png">
107
+ <img alt="quorum web dashboard" src="https://raw.githubusercontent.com/kvndhrty/quorum/main/docs/images/web-light.png">
108
+ </picture>
109
+
110
+ ![quorum terminal dashboard](https://raw.githubusercontent.com/kvndhrty/quorum/main/docs/images/tui.png)
111
+
112
+ ## Adopt a live session
113
+
114
+ The work is already underway in an interactive session? Don't re-queue it —
115
+ adopt it:
116
+
117
+ ```bash
118
+ quorum integration install codex # once per harness (also: opencode, claude-code)
119
+ quorum task adopt "refactoring the auth flow" # from the session's directory
120
+ ```
121
+
122
+ The session becomes an *attached* task: the manager observes it (liveness,
123
+ git state, reports) but never runs it — your nudges and the manager's pokes
124
+ are delivered *inside* the live session by the harness's hook the next time
125
+ it stops. `quorum task detach` hands it back to the headless runner. See
126
+ [docs/guide.md#adopting-a-live-session](https://github.com/kvndhrty/quorum/blob/main/docs/guide.md#adopting-a-live-session)
127
+ and the per-harness adapters under
128
+ [integrations/](https://github.com/kvndhrty/quorum/tree/main/integrations).
129
+
130
+ ## How it works
131
+
132
+ - **A task is a sequence of harness runs.** Each run executes in the task's
133
+ own git worktree (`~/.quorum/worktrees/<id>`), so parallel tasks on one
134
+ repo never collide and your checkout stays clean. Session ids are captured
135
+ from the harness's output so follow-up runs can `--resume`.
136
+ - **Status is the harness's own words.** The run prompt teaches a simple
137
+ protocol — report progress with `quorum task report`, check guidance with
138
+ `quorum task inbox` — and the conventional flow is
139
+ `planning → executing → reviewing → pr → done`. Quorum records what the
140
+ harness says; it never enforces a state machine.
141
+ - **The manager is an agent, not a ruleset.** Each cycle it compiles a
142
+ situation digest — including its own recent actions and whether they
143
+ changed anything — and your harness decides what to do, with real
144
+ authority: `task run`, `task nudge`, `task add`, `task cancel`, escalate.
145
+ Every action is auto-journaled (`quorum manager journal`); the journal
146
+ feeds back into the next digest so the manager never loops on an
147
+ intervention that isn't working. Steer it with `quorum manager tell`.
148
+ - **Guidance is a message, not a keystroke.** Your nudges and the manager's
149
+ pokes travel the same file-based inbox; the next run starts with them in
150
+ its prompt, and a cooperative harness picks them up mid-run.
151
+ - **Failure is loud and recovery is automatic.** If your LLM service goes
152
+ down, every harness-driven tick fails visibly — and keeps being scheduled,
153
+ so the first tick after service returns reads the world from files and
154
+ relaunches whatever died. No degraded fallback mode to babysit.
155
+ - **All state is files** under `QUORUM_HOME` (default `~/.quorum`): task
156
+ records, transcripts, a message board, inboxes — written with atomic
157
+ tmp+rename. The TUI and web dashboard are pure readers and work even when
158
+ the supervisor is down. Copy the directory and your whole setup moves.
159
+
160
+ ## Supervision policy is a prompt
161
+
162
+ `~/.quorum/prompts/manager.md` is the manager's constitution: how patient it
163
+ is, when it escalates, how it words its pokes, when creating follow-up work
164
+ is warranted. Edit it to retune your manager; delete it to restore the
165
+ default. (An optional `[llm]` section separately gives *plugin* agents a
166
+ small-completion client — the manager and tasks run your full harness
167
+ directly.)
168
+
169
+ ## Optional sandbox
170
+
171
+ Quorum pairs naturally with [nono](https://github.com/nolabs-ai/nono)
172
+ (kernel-enforced sandboxing via Landlock/Seatbelt): wrap the whole thing with
173
+ `nono run --profile quorum -- quorum up`, or set `[sandbox] use_nono = true`
174
+ to confine each task run to its worktree plus `QUORUM_HOME`. Fails closed:
175
+ if sandboxing was requested and nono-py is missing, nothing runs unsandboxed.
176
+ See [docs/guide.md](https://github.com/kvndhrty/quorum/blob/main/docs/guide.md#sandboxing).
177
+
178
+ ## Customizing
179
+
180
+ - **Configure** harnesses, schedules, and the manager's action budget in
181
+ `config.toml` — quorum never rewrites that file.
182
+ - **Retune** the task preamble and the manager's policy by editing
183
+ `~/.quorum/prompts/*.md`; delete a file to restore the default.
184
+ - **Extend** with your own agents: drop a ~20-line Python file into
185
+ `~/.quorum/plugins/` — [examples/steward.py](https://github.com/kvndhrty/quorum/blob/main/examples/steward.py) is a
186
+ complete worked example (a rule-based file organizer with undo).
187
+
188
+ Everything above, in depth: **[docs/guide.md](https://github.com/kvndhrty/quorum/blob/main/docs/guide.md)**.
189
+ Design record: [docs/architecture.md](https://github.com/kvndhrty/quorum/blob/main/docs/architecture.md).
190
+
191
+ ## Development
192
+
193
+ ```bash
194
+ uv sync --all-extras
195
+ uv run pytest
196
+ uv run ruff check .
197
+ ```
198
+
199
+ Repo conventions and the layer-by-layer map live in
200
+ [CLAUDE.md](https://github.com/kvndhrty/quorum/blob/main/CLAUDE.md).