sibyl-system 1.2.2 → 1.3.1
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.
- package/README.md +57 -14
- package/dist/cli.js +393 -199
- package/dist/index.js +572 -175
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
A standalone [opencode](https://opencode.ai) plugin:
|
|
6
6
|
|
|
7
7
|
- **`sibyl_consult`** — a three-voter review council (MELCHIOR / BALTHASAR / CASPER)
|
|
8
|
-
that audits an artifact against a goal and returns a **fail-closed
|
|
8
|
+
that audits an artifact against a goal and returns a **fail-closed verdict**
|
|
9
|
+
(`APPROVE` / `REJECT` / `CANNOT_ANSWER`).
|
|
9
10
|
- **`sibyl_swarm`** — a lightweight workflow swarm: an ARCHITECT persona plans a
|
|
10
11
|
dependency-ordered task graph, deterministic workers execute it in waves, and
|
|
11
12
|
the result is aggregated into a verdict.
|
|
@@ -24,8 +25,10 @@ Council voters, swarm workers and the swarm judge run as **child sessions of the
|
|
|
24
25
|
calling session** (`parentID` from the tool context): the TUI session picker
|
|
25
26
|
lists root sessions only, so no sibyl ballot ever clutters your top-level
|
|
26
27
|
session list. Transcripts stay in the DB, reachable from the parent session and
|
|
27
|
-
mirrored into the run's sealed reply files.
|
|
28
|
-
to top-level creation
|
|
28
|
+
mirrored into the run's sealed reply files. A seatless (CLI/headless) caller
|
|
29
|
+
falls back to top-level creation, and its voter rows are **garbage-collected
|
|
30
|
+
on success** (the reply files are the durable record; failed votes keep their
|
|
31
|
+
DB rows for salvage). `sibyl_review` roles run fully isolated under /tmp with
|
|
29
32
|
their own HOME/DB (`src/lane/isolated.ts`), never touching yours.
|
|
30
33
|
|
|
31
34
|
## v1.1 — the review chamber (民主集中制)
|
|
@@ -44,6 +47,7 @@ Laws: docs/isolation-laws.md. Mechanism: docs/democratic-centralism.md.
|
|
|
44
47
|
node dist/cli.js run --target doc.md --goal "Is this sound?" --model your-provider/your-model
|
|
45
48
|
node dist/cli.js status --tail 5
|
|
46
49
|
node dist/cli.js spotcheck <runId>
|
|
50
|
+
node dist/cli.js run --target - --profile exam --scenario scenarios/<id>.json ... # behavioral exam venue (bell preflight mandatory)
|
|
47
51
|
```
|
|
48
52
|
|
|
49
53
|
## Install
|
|
@@ -94,7 +98,7 @@ stderr, registers nothing, and returns empty hooks.
|
|
|
94
98
|
| Tool | Arguments | Behavior |
|
|
95
99
|
|------|-----------|----------|
|
|
96
100
|
| `sibyl_consult` | `{ artifact, goal }` | `artifact` is a file path or inline multi-line text (≤ 256 KiB). The three councilors audit it in parallel; each reply is parsed into a verdict (one in-session JSON-only repair shot per voter). Returns the tally, merged reasons/must-fix, run id, and per-voter reply file paths. |
|
|
97
|
-
| `sibyl_swarm` | `{ artifact, goal, judge? }` | ARCHITECT decomposes goal + artifact into a strict-JSON workflow schema; workers are minted deterministically and dispatched in dependency waves; drafts land in the run's space dir. Verdict: `APPROVE` / `REJECT` / `EXHAUSTED
|
|
101
|
+
| `sibyl_swarm` | `{ artifact, goal, judge? }` | ARCHITECT decomposes goal + artifact into a strict-JSON workflow schema; workers are minted deterministically and dispatched in dependency waves; drafts land in the run's space dir. Verdict: `APPROVE` / `REJECT` / `EXHAUSTED`, forced to `CANNOT_ANSWER` when a declared worker left no terminal row (W2) or zero drafts were produced. With `judge: true`, one extra judge pass may replace the derived verdict — an unrecognized or failed judge reply keeps the derived one. |
|
|
98
102
|
| `sibyl_status` | `{ runId? }` | Read-only. Lists all recorded runs (newest last) + the last chamber-ledger rows, or shows one run's full record and space dir. |
|
|
99
103
|
| `sibyl_review` | `{ target, goal, seed?, maxRounds? }` | v1.1 general democratic-centralism chamber: launches the isolated evidence→clash→judge pipeline detached and returns a receipt (explicitly NOT a verdict); the single voice lands in the run record at terminal state. CLI twin: `sibyl-chamber`. |
|
|
100
104
|
| `sibyl_anchor_check` | `{ sessionId, index?, body?, expectSha256?, label? }` | Read-only verifier for "human anchor" claims (an order/approval message the owner says they sent): re-checks the claim against the engine's own session store instead of trusting a transcribed string. Closed 4-state verdict `MATCH` / `MISMATCH` / `ABSENT` / `ERROR`; the receipt carries a hash of the canonical view read, so later store mutation shows up as a view-hash difference. No `expectSha256` claim → non-verdict. |
|
|
@@ -119,7 +123,7 @@ defaults.
|
|
|
119
123
|
| `timeoutMs` | `240000` | Per-child-session timeout (create + prompt share the budget). Integer ≥ 1. |
|
|
120
124
|
| `concurrencyK` | `4` | Cap on parallel workers per wave (also capped by the schema's own `concurrency`). Integer 1–8. |
|
|
121
125
|
| `staggerMs` | `2000` | Delay between worker launches within a wave, to avoid rate-limit bursts. Integer ≥ 0. |
|
|
122
|
-
| `lane` | see below | v1.1 isolated-lane mechanics for `sibyl_review` / `sibyl-chamber` (`src/lane/isolated.ts`). Sub-keys: `runRoot` (string, default `"/tmp"`) — parent dir for run dirs; `opencodeBin` (non-empty string; discovery order: `$OPENCODE_BIN` → `~/.local/bin/opencode` → `/usr/local/bin/opencode` → `/usr/bin/opencode` → bare PATH name `"opencode"`; never empty, a wrong guess fails loud at spawn naming the bin) — the binary launched for each isolated role; `configSource` (default `~/.config/opencode/opencode.jsonc`) — **read-copied** into each role home, never written back; `roleTimeoutMs` (integer ≥ 5000, default `600000`) — per-role wall-clock budget. |
|
|
126
|
+
| `lane` | see below | v1.1 isolated-lane mechanics for `sibyl_review` / `sibyl-chamber` (`src/lane/isolated.ts`). Sub-keys: `runRoot` (string, default `"/tmp"`) — parent dir for run dirs; `opencodeBin` (non-empty string; discovery order: `$OPENCODE_BIN` → `~/.local/bin/opencode` → `/usr/local/bin/opencode` → `/usr/bin/opencode` → bare PATH name `"opencode"`; never empty, a wrong guess fails loud at spawn naming the bin) — the binary launched for each isolated role; `configSource` (default `~/.config/opencode/opencode.jsonc`) — **read-copied** into each role home, never written back; `roleTimeoutMs` (integer ≥ 5000, default `600000`) — per-role wall-clock budget; `examMaskDirs` (array of absolute paths, default `[]`) — dirs tmpfs-masked from exam candidates (grader files, other runs); empty = an unsandboxed venue that says so plainly on the voice face. |
|
|
123
127
|
| `modelPolicy` | `{ allowedPrefixes: ["local-"] }` | v1.1 E4/F1 seating allow-list: only pool models whose `providerID/modelID` starts with one of these prefixes may take a seat; everything else is denied at launch. Array of non-empty strings, **at least 1 entry** (an empty list is rejected as a deny-everything typo). Set this to your own machine's providers — the shipped default is a prefix pattern, not a specific model. |
|
|
124
128
|
| `chamber` | see below | v1.1 review-chamber seats and rounds. Sub-keys: `maxRounds` (integer 1–8, default `3`) — clash/cross-critique round cap; `roles` (`{ evidence, pro, con, judge }` → pool slot names, each default `"default"`); `judgePool` (non-empty array of slot names, default `["default"]`) — the slots the independent judge may be drawn from (E2 pool+seed committed before launch). |
|
|
125
129
|
|
|
@@ -140,6 +144,8 @@ fails the release if any vendor model id reappears on a shipped surface.
|
|
|
140
144
|
|
|
141
145
|
## State layout
|
|
142
146
|
|
|
147
|
+
- **Chamber ledger**: `~/.sibyl/chamber-ledger.jsonl` — append-only, one JSON
|
|
148
|
+
record per line, serial-stamped at append (A5); `sibyl_status` reads its tail.
|
|
143
149
|
- **Runs file**: `~/.sibyl/runs.json` (Windows: `%USERPROFILE%\.sibyl\runs.json`) —
|
|
144
150
|
one record per run (id, kind, status, verdict tally, operator notes), co-located
|
|
145
151
|
with `spaces/` under the one `~/.sibyl` state root, so npm upgrades (which ship a
|
|
@@ -158,16 +164,45 @@ fails the release if any vendor model id reappears on a shipped surface.
|
|
|
158
164
|
Plainly stated:
|
|
159
165
|
|
|
160
166
|
- Approval needs **≥ 2 of 3 approvals and zero error/missing votes**.
|
|
161
|
-
|
|
167
|
+
- When the council genuinely spoke (all three ballots substantive, or ≥ 2
|
|
168
|
+
substantive rejects) and approval was not reached, the verdict is **REJECT**.
|
|
169
|
+
- When error/missing seats leave no decision (2A + 1 error, 2A + 1 missing,
|
|
170
|
+
a tie with an error seat), the verdict is **CANNOT_ANSWER** — a wounded
|
|
171
|
+
instrument, NOT a substantive reject, and it can never approve. Repair the
|
|
172
|
+
infra and re-run (since v1.2; pinned by `test/council.test.ts`).
|
|
162
173
|
- Error votes, missing votes, and malformed verdicts all count against approval.
|
|
163
174
|
- A reply that still doesn't parse after its single repair shot becomes a
|
|
164
175
|
0-confidence REJECT ballot with reason `verdict-unparseable: …`.
|
|
165
176
|
- There is no lenient mode, by design.
|
|
166
177
|
|
|
178
|
+
## Ballot-integrity laws (W1–W4, drafted 2026-10-04/05 after the campaign postmortems; shipped in v1.3.0)
|
|
179
|
+
|
|
180
|
+
- **Convener recusal (W1).** Every consult/swarm/review run resolves the
|
|
181
|
+
convening execution chain through the engine's own session store
|
|
182
|
+
(`session.get` parent links — never a self-report) and scans it for drafting
|
|
183
|
+
evidence of the artifact (write/edit parts targeting it, or content matches).
|
|
184
|
+
A kinship hit stamps the run `independence=NOT-INDEPENDENT`: the ballot is
|
|
185
|
+
preserved as data but is not in any effective path. A chain that cannot be
|
|
186
|
+
read stamps `UNVERIFIABLE` — unreadable is never silently clean.
|
|
187
|
+
- **Absence is a disability (W2).** Before any tally is honored, every declared
|
|
188
|
+
voter/worker must have left a terminal row (`ok|error|timeout` + detail) in
|
|
189
|
+
the run's `TERMINALS.txt`. A declared seat without its row is counted
|
|
190
|
+
`dead-without-record=N` on the first receipt line and forces `CANNOT_ANSWER` —
|
|
191
|
+
never folded into an approve or a reject. A worker session ending with empty
|
|
192
|
+
content is a failure, not a done ballot.
|
|
193
|
+
- **The ruler rides the ballot (W3).** Each terminal record embeds
|
|
194
|
+
`instrument: {rulesHash, components}` — the sha256 of every persona prompt /
|
|
195
|
+
criteria text the build actually sends (councilors, architect, judge, repair
|
|
196
|
+
grammar, chamber roles). `rulesHash` flips with any ruler edit, so
|
|
197
|
+
"re-evaluate under the new rules" is reproducible and comparable; the
|
|
198
|
+
`rules=<12hex>` label rides the receipt face and `sibyl_status` lines.
|
|
199
|
+
|
|
167
200
|
## Security notes
|
|
168
201
|
|
|
169
202
|
- Consult/swarm send the artifact text to your configured model pool providers.
|
|
170
|
-
- The plugin writes
|
|
203
|
+
- The plugin writes under its state paths (the runs file and the per-run space) and,
|
|
204
|
+
for chamber/exam lanes, sealed run directories under `lane.runRoot`
|
|
205
|
+
(default `/tmp`) — role homes, transcripts, pidfiles, CHECKSUMS.
|
|
171
206
|
- Voter/worker turns run with `bash`, `edit`, and `write` disabled per prompt.
|
|
172
207
|
|
|
173
208
|
## Architecture (`src/`)
|
|
@@ -184,7 +219,9 @@ index.ts plugin entry: parse options → share one RunStore + client
|
|
|
184
219
|
│ validation → exactly one repair → fail-closed REJECT
|
|
185
220
|
├── council/ councilor personas + tallyVotes (majority2of3 default,
|
|
186
221
|
│ unanimous exported); pure aggregation, zero IO
|
|
187
|
-
├── state/ RunStore: atomic RMW runs.json, per-run space dirs
|
|
222
|
+
├── state/ RunStore: atomic RMW runs.json, per-run space dirs;
|
|
223
|
+
│ v1.1 chamber records: EOF-append ledger (A5), face-last
|
|
224
|
+
│ regeneration, CHECKSUMS + spotcheck (A4)
|
|
188
225
|
├── swarm/ planner (ARCHITECT schema) → minter (deterministic worker
|
|
189
226
|
│ roster) → dispatcher (dependency waves, stagger,
|
|
190
227
|
│ suspend-on-rate-limit, resume) → aggregate (report +
|
|
@@ -196,9 +233,15 @@ index.ts plugin entry: parse options → share one RunStore + client
|
|
|
196
233
|
│ fail-closed, dissent sealed by path+hash)
|
|
197
234
|
├── exam/ v1.1 E3: scenario-as-data behavioral exams, mechanical
|
|
198
235
|
│ transcript/disk signal grading, canary veto
|
|
199
|
-
├──
|
|
200
|
-
│
|
|
201
|
-
├──
|
|
236
|
+
├── personas.ts registry: 3 councilors + ARCHITECT + judge/repair texts,
|
|
237
|
+
│ model slots — the single source the W3 face hashes
|
|
238
|
+
├── terminal.ts W2 terminal-row grammar (ok|error|timeout), parse + the
|
|
239
|
+
│ dead-without-record declared-minus-recorded diff
|
|
240
|
+
├── instrument.ts W3 ruler face: sha256 set of the live prompt/criteria
|
|
241
|
+
│ texts + deterministic rulesHash fold
|
|
242
|
+
├── independence.ts W1 convener-recusal: engine-store chain walk +
|
|
243
|
+
│ drafting-evidence scan (INDEPENDENT / NOT-INDEPENDENT /
|
|
244
|
+
│ UNVERIFIABLE, fail-closed on unreadable)
|
|
202
245
|
├── options.ts zod v4 schema + parseOptions (never throws)
|
|
203
246
|
├── cli.ts v1.1 sibyl-chamber bin: run | status | spotcheck | kill
|
|
204
247
|
└── tools/ sibyl_consult / sibyl_swarm / sibyl_status / sibyl_review
|
|
@@ -210,7 +253,7 @@ index.ts plugin entry: parse options → share one RunStore + client
|
|
|
210
253
|
|
|
211
254
|
This is **not** the oh-my-openagent swarm. It has no `team_*` tools, no
|
|
212
255
|
cross-agent message bus, and no fleet-orchestration dependency of any kind —
|
|
213
|
-
it is a clean-room implementation (~
|
|
256
|
+
it is a clean-room implementation (~5K lines of product code, comments excluded) that happens to
|
|
214
257
|
share the problem space. `sibyl_swarm` is a single tool call driving a
|
|
215
258
|
PLAN→MINT→DISPATCH→AGGREGATE pipeline over ordinary child sessions.
|
|
216
259
|
|
|
@@ -218,7 +261,7 @@ PLAN→MINT→DISPATCH→AGGREGATE pipeline over ordinary child sessions.
|
|
|
218
261
|
|
|
219
262
|
```bash
|
|
220
263
|
npm run typecheck # tsc --noEmit, strict + noUncheckedIndexedAccess + exactOptionalPropertyTypes
|
|
221
|
-
npm test #
|
|
264
|
+
npm test # full unit suite, fully offline (no network, no LLM; ~420 at v1.3.0 — the run prints the exact count)
|
|
222
265
|
npm run build # esbuild bundle → dist/index.js + dist/cli.js (ESM)
|
|
223
266
|
node smoke/run-smoke.mjs # offline smoke of the shipped surface (see smoke/README.md)
|
|
224
267
|
./ship.sh # the full release gate: all of the above + portability scan of the packed tarball
|
|
@@ -250,4 +293,4 @@ MIT
|
|
|
250
293
|
|
|
251
294
|
## Release wheel
|
|
252
295
|
|
|
253
|
-
Tags v* require a packet receipt (docs/release/<ver>.md with a real `gate: PASS` line) and a lease; after cloning run `scripts/install-hooks.sh` — hooks are per-clone and ship empty.
|
|
296
|
+
Tags v* require a packet receipt (docs/release/<ver>.md with a real `gate: PASS` line) and a lease; after cloning run `scripts/install-hooks.sh` — hooks are per-clone and ship empty. **W4 (2026-10-05):** the hook reads the receipt AND `package.json`'s version exclusively from the pushed tag's **object body** (`git show <oid>:<path>`); working-tree reads are forbidden — the tag must carry its own paperwork, and the in-tag version must equal the tag name (ghost-version class killed). Regression-locked in `test/hook-prepush.test.ts` (ref lines fed via stdin).
|