@balpal4495/quorum 0.1.8 → 0.1.9
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 +235 -110
- package/bin/init.js +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,48 +1,45 @@
|
|
|
1
1
|
# Quorum
|
|
2
2
|
|
|
3
|
-
Quorum
|
|
3
|
+
**Quorum gives AI agents memory and judgment.**
|
|
4
4
|
|
|
5
|
-
Drop
|
|
5
|
+
Drop it into any Node.js project, wire up your LLM, and your agents can query what's been tried before, validate decisions against prior evidence, and write new knowledge back — with a human approving every write.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npx @balpal4495/quorum@latest init
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
That's it. Quorum copies itself into your project, merges instruction files for your AI, and creates the knowledge store directory. Run `npm install` and you're ready.
|
|
8
12
|
|
|
9
13
|
---
|
|
10
14
|
|
|
11
|
-
##
|
|
15
|
+
## Why this exists
|
|
16
|
+
|
|
17
|
+
When AI agents work in a codebase over weeks or months, they lose context between sessions. They retry approaches that already failed. They contradict previous decisions. They have no memory of what the team has already learned.
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
Quorum solves this with four modules:
|
|
14
20
|
|
|
15
21
|
| Module | What it does |
|
|
16
22
|
|---|---|
|
|
17
|
-
| **Oracle** |
|
|
18
|
-
| **Jury** |
|
|
19
|
-
| **Council** |
|
|
20
|
-
| **Sentinel** |
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
oracle.query() → jury.evaluate() → council.deliberate() → human gate → Executor
|
|
24
|
-
sentinel.coverage() + sentinel.detectDrift() → advisory test output
|
|
25
|
-
```
|
|
23
|
+
| **Oracle** | Stores and retrieves project knowledge — decisions, investigations, outcomes |
|
|
24
|
+
| **Jury** | Scores a proposed design against that knowledge — gives you confidence before acting |
|
|
25
|
+
| **Council** | A panel of advisors challenges the design and a Chairman gives a final verdict |
|
|
26
|
+
| **Sentinel** | Shows you which parts of the codebase the AI knows nothing about — and flags stale knowledge |
|
|
26
27
|
|
|
27
28
|
---
|
|
28
29
|
|
|
29
30
|
## How it works
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
Every significant decision goes through a pipeline before execution:
|
|
32
33
|
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Agent[AI Agent] -->|query| Oracle
|
|
36
|
-
Oracle -->|evidence| Jury
|
|
37
|
-
Jury -->|scores| Council
|
|
38
|
-
Council -->|verdict| Gate[Human Gate]
|
|
39
|
-
Oracle -. reads .-> Chronicle[(Chronicle)]
|
|
40
|
-
Gate -. approved commit .-> Chronicle
|
|
41
|
-
Chronicle -. coverage + drift .-> Sentinel
|
|
42
|
-
Sentinel -. advisory report .-> CI([CI / Developer])
|
|
34
|
+
```
|
|
35
|
+
oracle.query() → jury.evaluate() → council.deliberate() → human gate → Executor
|
|
43
36
|
```
|
|
44
37
|
|
|
45
|
-
**
|
|
38
|
+
1. **Query** — retrieve everything Chronicle knows about the problem
|
|
39
|
+
2. **Evaluate** — Jury scores the proposed design against that evidence (0–1 confidence)
|
|
40
|
+
3. **Deliberate** — Council advisors challenge it independently, reviewers anonymously critique, Chairman gives a verdict
|
|
41
|
+
4. **Human gate** — if satisfied, a human approves the Chronicle entry; nothing is written automatically
|
|
42
|
+
5. **Execute** — agent proceeds with a validated, documented decision
|
|
46
43
|
|
|
47
44
|
```mermaid
|
|
48
45
|
sequenceDiagram
|
|
@@ -54,149 +51,277 @@ sequenceDiagram
|
|
|
54
51
|
participant Chronicle
|
|
55
52
|
|
|
56
53
|
Agent->>Oracle: query(text)
|
|
57
|
-
Oracle->>Chronicle: vector search
|
|
58
|
-
Chronicle-->>
|
|
59
|
-
Oracle-->>Agent: OracleResult[]
|
|
54
|
+
Oracle->>Chronicle: vector + BM25 search
|
|
55
|
+
Chronicle-->>Agent: ranked evidence
|
|
60
56
|
|
|
61
57
|
Agent->>Jury: evaluate(design, evidence)
|
|
62
|
-
Jury-->>Agent: score
|
|
58
|
+
Jury-->>Agent: confidence score + gaps
|
|
63
59
|
|
|
64
|
-
Agent->>Council: deliberate(design,
|
|
65
|
-
Council-->>Agent:
|
|
60
|
+
Agent->>Council: deliberate(design, evidence, jury_output)
|
|
61
|
+
Council-->>Agent: verdict + proposal
|
|
66
62
|
|
|
67
|
-
alt Council
|
|
68
|
-
|
|
69
|
-
else Council satisfied
|
|
70
|
-
Agent->>Human: surface verdict and proposal
|
|
63
|
+
alt Council satisfied
|
|
64
|
+
Agent->>Human: surface verdict for approval
|
|
71
65
|
Human->>Oracle: commit(proposalId)
|
|
72
|
-
Oracle->>Chronicle:
|
|
73
|
-
|
|
66
|
+
Oracle->>Chronicle: index entry
|
|
67
|
+
else not satisfied
|
|
68
|
+
Note over Agent: revise and retry
|
|
74
69
|
end
|
|
75
70
|
```
|
|
76
71
|
|
|
77
72
|
---
|
|
78
73
|
|
|
79
|
-
##
|
|
74
|
+
## Real examples
|
|
80
75
|
|
|
81
|
-
|
|
76
|
+
### Example 1 — An agent remembers a past failure
|
|
82
77
|
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
Your agent is about to propose JWT with symmetric signing. Oracle returns an entry:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
[abc-123] Tried symmetric JWT (HS256) in March. Rejected — no way to rotate keys
|
|
82
|
+
without invalidating all active sessions. Use RS256 with short-lived tokens.
|
|
83
|
+
confidence: 0.91 · status: committed
|
|
85
84
|
```
|
|
86
85
|
|
|
87
|
-
|
|
86
|
+
Jury flags this as a conflict. The agent revises to RS256 before Council even sees it.
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
---
|
|
90
89
|
|
|
91
|
-
|
|
90
|
+
### Example 2 — Validating a database migration plan
|
|
92
91
|
|
|
93
|
-
|
|
92
|
+
An agent proposes adding a `NOT NULL` column to a 50M-row table.
|
|
94
93
|
|
|
95
|
-
|
|
94
|
+
```typescript
|
|
95
|
+
const evidence = await oracle.query("schema migrations large tables")
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
const jury = await evaluate({
|
|
98
|
+
outcome: "Add NOT NULL column users.verified",
|
|
99
|
+
design: "ALTER TABLE, backfill with default false, then add constraint",
|
|
100
|
+
evidence,
|
|
101
|
+
})
|
|
102
|
+
// jury.confidence: 0.41 — gaps: ["no lock strategy", "no rollback plan"]
|
|
98
103
|
|
|
99
|
-
|
|
100
|
-
.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
const verdict = await deliberate({
|
|
105
|
+
outcome: "Add NOT NULL column users.verified",
|
|
106
|
+
design: "ALTER TABLE, backfill with default false, then add constraint",
|
|
107
|
+
evidence,
|
|
108
|
+
jury_output: jury,
|
|
109
|
+
})
|
|
110
|
+
// verdict.satisfied: false
|
|
111
|
+
// verdict.verdict: "No lock strategy specified. On a table this size, a naive ALTER TABLE
|
|
112
|
+
// will take an exclusive lock for minutes. Use a shadow column pattern
|
|
113
|
+
// or pg_repack."
|
|
104
114
|
```
|
|
105
115
|
|
|
106
|
-
|
|
116
|
+
The agent revises the plan. Chronicle records the reasoning once approved.
|
|
107
117
|
|
|
108
118
|
---
|
|
109
119
|
|
|
110
|
-
|
|
120
|
+
### Example 3 — Onboarding a new AI to an established project
|
|
111
121
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
On day one, a fresh AI session queries Chronicle before touching anything:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const evidence = await oracle.query("authentication, session handling, token strategy")
|
|
126
|
+
// Returns 6 entries covering prior decisions, a failed experiment with Redis sessions,
|
|
127
|
+
// the current RS256 approach, and a note about the upcoming OAuth migration.
|
|
128
|
+
```
|
|
117
129
|
|
|
118
|
-
The
|
|
130
|
+
The AI works with full context from the first message — no archaeology through git history.
|
|
119
131
|
|
|
120
132
|
---
|
|
121
133
|
|
|
122
|
-
##
|
|
134
|
+
## Quick start
|
|
123
135
|
|
|
124
|
-
|
|
136
|
+
```typescript
|
|
137
|
+
import { setup } from "./quorum/modules/setup"
|
|
125
138
|
|
|
126
|
-
|
|
139
|
+
const { oracle, evaluate, deliberate } = await setup({
|
|
140
|
+
llm: myLLMProvider, // any function that calls your LLM — see wiring below
|
|
141
|
+
})
|
|
127
142
|
|
|
128
|
-
|
|
143
|
+
// Query what Chronicle knows
|
|
144
|
+
const evidence = await oracle.query("authentication patterns in this codebase")
|
|
129
145
|
|
|
130
|
-
|
|
146
|
+
// Evaluate a proposed design
|
|
147
|
+
const jury = await evaluate({
|
|
148
|
+
outcome: "Add OAuth2 login via GitHub",
|
|
149
|
+
design: "Use passport-github2, store sessions in Redis, 1-hour TTL",
|
|
150
|
+
evidence,
|
|
151
|
+
})
|
|
131
152
|
|
|
132
|
-
|
|
153
|
+
// Get a Council verdict
|
|
154
|
+
const verdict = await deliberate({
|
|
155
|
+
outcome: "Add OAuth2 login via GitHub",
|
|
156
|
+
design: "Use passport-github2, store sessions in Redis, 1-hour TTL",
|
|
157
|
+
evidence,
|
|
158
|
+
jury_output: jury,
|
|
159
|
+
})
|
|
133
160
|
|
|
134
|
-
|
|
161
|
+
if (verdict.satisfied) {
|
|
162
|
+
// → surface verdict.proposal to a human for approval
|
|
163
|
+
// → human calls oracle.commit(proposalId) to index it
|
|
164
|
+
// → Executor proceeds
|
|
165
|
+
} else {
|
|
166
|
+
// verdict.verdict contains the specific objection
|
|
167
|
+
// verdict.recommendation is "redesign" or "investigate-more"
|
|
168
|
+
}
|
|
169
|
+
```
|
|
135
170
|
|
|
136
|
-
|
|
171
|
+
---
|
|
137
172
|
|
|
138
|
-
|
|
173
|
+
## Wiring your LLM
|
|
139
174
|
|
|
140
|
-
|
|
175
|
+
Quorum accepts any function with this signature — you're never locked in:
|
|
141
176
|
|
|
142
177
|
```typescript
|
|
143
|
-
import {
|
|
144
|
-
|
|
178
|
+
import type { LLMProvider } from "./quorum/modules/shared/types"
|
|
179
|
+
```
|
|
145
180
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
181
|
+
```typescript
|
|
182
|
+
// Anthropic
|
|
183
|
+
const llm: LLMProvider = async (messages, model = "claude-3-5-sonnet-20241022") => {
|
|
184
|
+
const system = messages.find(m => m.role === "system")?.content ?? ""
|
|
185
|
+
const user = messages.filter(m => m.role !== "system")
|
|
186
|
+
const res = await anthropic.messages.create({ model, system, messages: user, max_tokens: 2048 })
|
|
187
|
+
return res.content[0].type === "text" ? res.content[0].text : ""
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// OpenAI
|
|
191
|
+
const llm: LLMProvider = async (messages, model = "gpt-4o") => {
|
|
192
|
+
const res = await openai.chat.completions.create({ model, messages })
|
|
193
|
+
return res.choices[0].message.content ?? ""
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Per-step model overrides (optional)
|
|
197
|
+
const { oracle, evaluate, deliberate } = await setup({
|
|
198
|
+
llm,
|
|
199
|
+
models: {
|
|
200
|
+
jury: "gpt-4o-mini",
|
|
201
|
+
council: {
|
|
202
|
+
frame: "gpt-4o-mini",
|
|
203
|
+
advisors: "gpt-4o-mini",
|
|
204
|
+
reviewers: "gpt-4o",
|
|
205
|
+
chairman: "gpt-4o",
|
|
206
|
+
},
|
|
207
|
+
},
|
|
151
208
|
})
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Oracle requires no LLM — only Jury, Council, and Sentinel drift checks need one.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Chronicle — the knowledge store
|
|
216
|
+
|
|
217
|
+
Chronicle lives at `.chronicle/` in your project root. It persists across sessions, machines, and contributors.
|
|
152
218
|
|
|
153
|
-
|
|
219
|
+
```
|
|
220
|
+
.chronicle/
|
|
221
|
+
committed/ ← approved entries as JSON (commit these to git)
|
|
222
|
+
proposals/ ← staged entries awaiting approval (commit these too — they're human-readable)
|
|
223
|
+
SUMMARY.md ← auto-generated weekly context, rebuilt on every commit
|
|
154
224
|
```
|
|
155
225
|
|
|
156
|
-
|
|
226
|
+
**The write path is always human-gated:**
|
|
157
227
|
|
|
158
|
-
|
|
228
|
+
```
|
|
229
|
+
oracle.propose() ← AI stages a candidate entry (no indexing yet)
|
|
230
|
+
oracle.commit() ← human approves — entry is indexed and searchable
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`deliberate()` automatically calls `oracle.propose()` at the end of every Council run. You only need to call `oracle.commit(proposalId)` when you're ready to approve it.
|
|
234
|
+
|
|
235
|
+
There are no auto-commits. Ever.
|
|
236
|
+
|
|
237
|
+
---
|
|
159
238
|
|
|
160
|
-
|
|
239
|
+
## Sentinel — codebase coverage and drift
|
|
161
240
|
|
|
162
|
-
|
|
241
|
+
Sentinel answers three questions Chronicle can't answer about itself.
|
|
163
242
|
|
|
243
|
+
### Which files does the AI know nothing about?
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { coverage } from "./quorum/modules/sentinel"
|
|
247
|
+
|
|
248
|
+
const report = await coverage(".chronicle", "src")
|
|
249
|
+
// report.percentage — 34%
|
|
250
|
+
// report.uncoveredFiles — ["src/auth/session.ts", "src/payments/stripe.ts", ...]
|
|
164
251
|
```
|
|
165
|
-
## Sentinel — Chronicle Coverage Map — 2026-W20
|
|
166
252
|
|
|
167
|
-
|
|
168
|
-
|----------|----------|---------|-------|-------------|--------|
|
|
169
|
-
| council/ | 0% | 0 | 8 | — | high |
|
|
170
|
-
| jury/ | 0% | 0 | 4 | — | high |
|
|
171
|
-
| oracle/ | 22% | 4 | 9 | — | medium |
|
|
172
|
-
| scripts/ | 0% | 0 | 1 | **1 files** | high |
|
|
173
|
-
| sentinel/| 0% | 0 | 5 | **2 files** | high |
|
|
174
|
-
| shared/ | 100% | 2 | 1 | — | low |
|
|
253
|
+
### Is the AI's knowledge stale?
|
|
175
254
|
|
|
176
|
-
|
|
177
|
-
|
|
255
|
+
```typescript
|
|
256
|
+
import { detectDrift } from "./quorum/modules/sentinel"
|
|
178
257
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
- `[30bdc1c1]` schema constraints not LLM self-evaluation — validated (0.88)
|
|
258
|
+
const report = await detectDrift(".chronicle", "src", llm)
|
|
259
|
+
// report.flags — entries where the key_insight may no longer match the code
|
|
182
260
|
```
|
|
183
261
|
|
|
184
|
-
|
|
262
|
+
### Coverage as CI assertions
|
|
185
263
|
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
264
|
+
```typescript
|
|
265
|
+
import { describe } from "vitest"
|
|
266
|
+
import { sentinelAssertions } from "./quorum/modules/sentinel"
|
|
267
|
+
|
|
268
|
+
describe("sentinel", () => {
|
|
269
|
+
sentinelAssertions({
|
|
270
|
+
chronicleDir: ".chronicle",
|
|
271
|
+
codebasePath: "src",
|
|
272
|
+
llm: myLLMProvider, // omit to skip drift tests
|
|
273
|
+
minCoveragePercent: 50, // 0 = advisory only (default — safe for new projects)
|
|
274
|
+
}).forEach(a => a())
|
|
275
|
+
})
|
|
193
276
|
```
|
|
194
277
|
|
|
278
|
+
### PR coverage map
|
|
279
|
+
|
|
280
|
+
Add `.github/workflows/sentinel-pr.yml` (included in `quorum/`) to get a comment on every PR showing which modules are covered, which are blind spots, and which files the PR touches — as a table and a colour-coded Mermaid heatmap.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Modules at a glance
|
|
285
|
+
|
|
286
|
+
| Module | Needs LLM | Entry point |
|
|
287
|
+
|---|---|---|
|
|
288
|
+
| Oracle | No | `oracle.query()` / `oracle.propose()` / `oracle.commit()` |
|
|
289
|
+
| Jury | Yes | `evaluate(input, deps)` |
|
|
290
|
+
| Council | Yes | `deliberate(input, deps)` |
|
|
291
|
+
| Sentinel | Optional | `coverage()` / `detectDrift()` / `sentinelAssertions()` |
|
|
292
|
+
|
|
293
|
+
Full API reference: [modules/README.md](modules/README.md)
|
|
294
|
+
Design decisions (what not to change): [modules/CLAUDE.md](modules/CLAUDE.md)
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Dependencies
|
|
299
|
+
|
|
300
|
+
| Package | Why |
|
|
301
|
+
|---|---|
|
|
302
|
+
| `zod` | Validates all structured LLM output — required |
|
|
303
|
+
| `vectordb` | LanceDB embedded vector store — default adapter, swappable |
|
|
304
|
+
| `@xenova/transformers` | Local ONNX embedder (all-MiniLM-L6-v2) — default adapter, swappable |
|
|
305
|
+
|
|
306
|
+
`vectordb` and `@xenova/transformers` are optional if you bring your own vector store and embedder. Implement the `VectorStore` interface in `oracle/types.ts` and pass your own `embedder` function to `setup()`.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Releases
|
|
311
|
+
|
|
312
|
+
Quorum is published to npm as `@balpal4495/quorum`. New versions are released by pushing a semver tag:
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
git tag v0.2.0 && git push origin v0.2.0
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
GitHub Actions publishes to npm automatically via OIDC Trusted Publishing — no stored tokens.
|
|
319
|
+
|
|
195
320
|
---
|
|
196
321
|
|
|
197
|
-
##
|
|
322
|
+
## Docs
|
|
198
323
|
|
|
199
|
-
- [modules/README.md](modules/README.md) — full API reference
|
|
200
|
-
- [modules/AGENTS.md](modules/AGENTS.md) — file ownership and
|
|
201
|
-
- [modules/CLAUDE.md](modules/CLAUDE.md) — design decisions and
|
|
202
|
-
- [SETUP.md](SETUP.md) — bootstrap sequence for
|
|
324
|
+
- [modules/README.md](modules/README.md) — full API reference
|
|
325
|
+
- [modules/AGENTS.md](modules/AGENTS.md) — file ownership and what each file owns
|
|
326
|
+
- [modules/CLAUDE.md](modules/CLAUDE.md) — design decisions and invariants
|
|
327
|
+
- [SETUP.md](SETUP.md) — manual bootstrap sequence (for AI-assisted setup)
|
package/bin/init.js
CHANGED
|
@@ -15,6 +15,10 @@ import { promises as fs } from "fs"
|
|
|
15
15
|
import path from "path"
|
|
16
16
|
import { fileURLToPath } from "url"
|
|
17
17
|
import { execSync } from "child_process"
|
|
18
|
+
import { createRequire } from "module"
|
|
19
|
+
|
|
20
|
+
const _require = createRequire(import.meta.url)
|
|
21
|
+
const PKG_VERSION = _require("../package.json").version
|
|
18
22
|
|
|
19
23
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
20
24
|
const QUORUM_ROOT = path.resolve(__dirname, "..")
|
|
@@ -346,7 +350,7 @@ async function cli() {
|
|
|
346
350
|
}
|
|
347
351
|
|
|
348
352
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
349
|
-
console.log(
|
|
353
|
+
console.log(PKG_VERSION)
|
|
350
354
|
return
|
|
351
355
|
}
|
|
352
356
|
|