@balpal4495/quorum 0.4.2 → 2.0.0

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/modules/README.md CHANGED
@@ -1,11 +1,12 @@
1
- # Oracle · Jury · Council · Sentinel
1
+ # Advisor · Oracle · Jury · Council · Sentinel
2
2
 
3
- Four portable modules for the knowledge and reasoning layer of any agentic workflow.
3
+ Five portable modules for the knowledge and reasoning layer of any agentic workflow.
4
4
  Drop the `modules/` folder into your project and wire up the dependencies.
5
5
 
6
6
  ```
7
- OracleJury → Council → human gate → Executor
8
- Sentinelcoverage + drift + PR coverage map
7
+ Advisorplain-language questions answered from Chronicle
8
+ Oracle → Jury Council → human gate → Executor
9
+ Sentinel → coverage + drift + PR coverage map
9
10
  ```
10
11
 
11
12
  ---
@@ -14,6 +15,7 @@ Sentinel → coverage + drift + PR coverage map
14
15
 
15
16
  | Module | Responsibility | LLM? |
16
17
  |---|---|---|
18
+ | **Advisor** | Ask a plain-language question — synthesises Chronicle evidence into a concise answer with an internal validation loop | Yes |
17
19
  | **Oracle** | Query and write interface to Chronicle (the persistent knowledge store) | No |
18
20
  | **Jury** | Evaluate a design against Oracle evidence — produces a confidence score | Yes |
19
21
  | **Council** | Adversarial validation via parallel advisor/reviewer fan-out — produces a verdict | Yes |
@@ -107,18 +109,34 @@ Recommended `tsconfig.json` settings:
107
109
 
108
110
  ## Quick start
109
111
 
112
+ ```typescript
113
+ import { setup } from "./modules/setup"
114
+
115
+ // The simplest entry point — wires all modules from one call
116
+ const { oracle, evaluate, deliberate, ask } = await setup({ llm: myLLMProvider })
117
+
118
+ // Ask a plain-language question (Advisor)
119
+ const answer = await ask("what did the team decide about authentication?")
120
+ // answer.what_we_know, .recommendation, .next_step, .risks, .blockers, .retries
121
+
122
+ // Or run the full evaluation pipeline for a proposed design:
123
+ const evidence = await oracle.query("authentication patterns in this codebase")
124
+ ```
125
+
126
+ ### Manual wiring (without setup())
127
+
110
128
  ```typescript
111
129
  import { createOracleClient, xenovaEmbed, createLanceDBStore } from "./modules/oracle"
112
130
  import { evaluate } from "./modules/jury"
113
131
  import { deliberate } from "./modules/council"
114
132
 
115
- // 1. Wire Oracle (no LLM required)
133
+ // Wire Oracle manually
116
134
  const oracle = createOracleClient({
117
135
  embedder: xenovaEmbed,
118
136
  vectorStore: await createLanceDBStore(".chronicle"),
119
137
  })
120
138
 
121
- // 2. Retrieve evidence for the task at hand
139
+ // Retrieve evidence for the task at hand
122
140
  const evidence = await oracle.query("authentication patterns in this codebase")
123
141
 
124
142
  // 3. Jury evaluates the design against the evidence
@@ -167,6 +185,47 @@ if (verdict.satisfied) {
167
185
 
168
186
  ---
169
187
 
188
+ ## Advisor
189
+
190
+ The Advisor is the plain-language interface to Chronicle. Use it to answer questions rather than to evaluate designs. It is a **read-only** path — it never calls `oracle.propose()` or `oracle.commit()`.
191
+
192
+ ```typescript
193
+ import { ask } from "./modules/advisor"
194
+
195
+ const answer = await ask(
196
+ { question: "What did the team decide about session handling?", evidence },
197
+ { llm: myLLMProvider },
198
+ )
199
+ ```
200
+
201
+ Or via `setup()`, which queries Oracle automatically:
202
+
203
+ ```typescript
204
+ const { ask } = await setup({ llm: myLLMProvider })
205
+ const answer = await ask("What did the team decide about session handling?")
206
+ ```
207
+
208
+ ### Advisor output
209
+
210
+ ```typescript
211
+ interface AdvisorOutput {
212
+ question: string
213
+ confidence: number // 0–1 — how confident the answer is given the evidence
214
+ what_we_know: string // plain-language summary of relevant Chronicle knowledge
215
+ risks: string[] // real risks worth knowing
216
+ blockers: string[] // hard blockers — must be resolved before acting (often empty)
217
+ recommendation: string // one clear recommended action
218
+ next_step: string // specific next step or quorum command
219
+ retries: number // how many retry attempts were needed (0 = first try succeeded)
220
+ }
221
+ ```
222
+
223
+ ### Validation loop
224
+
225
+ Advisor validates its own answer before returning. If `confidence < 0.7` or `blockers.length > 0`, it retries the LLM call with the previous answer as context — up to 2 retries. After the retry budget is exhausted, the best answer is returned regardless. Throws on non-JSON or schema-invalid LLM output.
226
+
227
+ ---
228
+
170
229
  ## LLM provider interface
171
230
 
172
231
  The `LLMProvider` type is a simple function. Wire it to any provider:
@@ -284,14 +343,14 @@ const risk = classifyRisk(outcome, design, evidence)
284
343
  // risk.council_mode — "jury-only" | "lite" | "full"
285
344
  ```
286
345
 
287
- | Risk | Triggers | Advisor + Reviewer count |
288
- |---|---|---|
289
- | Low | Nothing sensitive detected | 1 + 1 |
290
- | Medium | Cache, queues, deployments, rate limiting | 1 + 2 |
291
- | High | DB migrations, permissions, PII, secrets | 5 + 5 |
292
- | Critical | Auth, payments, crypto, data deletion | 5 + 5 |
346
+ | Risk | Triggers | Council mode | Advisor + Reviewer |
347
+ |---|---|---|---|
348
+ | Low | Nothing sensitive detected | jury-only — skipped | 0 + 0 |
349
+ | Medium | Cache, queues, deployments, rate limiting | lite | 1 + 2 |
350
+ | High | DB migrations, permissions, PII, secrets | full | 5 + 5 |
351
+ | Critical | Auth, payments, crypto, data deletion | full + human flag | 5 + 5 |
293
352
 
294
- Refuted entries in the evidence pack always elevate risk by at least one level.
353
+ Refuted entries in the evidence pack always elevate risk by at least one level. `jury-only` means Council is not called at all — Jury alone is sufficient for low-risk designs.
295
354
 
296
355
  ### Council output routing
297
356
 
@@ -0,0 +1,87 @@
1
+ import { z } from "zod"
2
+ import type { AdvisorInput, AdvisorOutput, AdvisorAnswer, AdvisorDeps } from "./types"
3
+ import { SYSTEM_PROMPT, buildUserPrompt } from "./prompt"
4
+
5
+ const SATISFACTION_THRESHOLD = 0.7
6
+ const MAX_RETRIES = 2
7
+
8
+ const AdvisorAnswerSchema = z.object({
9
+ confidence: z.number().min(0).max(1),
10
+ what_we_know: z.string().min(1),
11
+ risks: z.array(z.string()),
12
+ blockers: z.array(z.string()),
13
+ recommendation: z.string().min(1),
14
+ next_step: z.string().min(1),
15
+ })
16
+
17
+ async function callLLM(
18
+ input: AdvisorInput,
19
+ deps: AdvisorDeps,
20
+ attempt: number,
21
+ previous: AdvisorAnswer | null,
22
+ ): Promise<AdvisorAnswer> {
23
+ const { llm, model } = deps
24
+
25
+ let userPrompt = buildUserPrompt(input.question, input.evidence)
26
+
27
+ if (attempt > 0 && previous) {
28
+ userPrompt += [
29
+ "",
30
+ `## Previous Answer (attempt ${attempt} — did not meet quality threshold)`,
31
+ `Confidence: ${previous.confidence.toFixed(2)} (need ≥ ${SATISFACTION_THRESHOLD})`,
32
+ previous.blockers.length > 0
33
+ ? `Unresolved blockers: ${previous.blockers.join("; ")}`
34
+ : "",
35
+ "Please produce a more specific and concrete answer.",
36
+ ].filter(Boolean).join("\n")
37
+ }
38
+
39
+ const raw = await llm(
40
+ [
41
+ { role: "system", content: SYSTEM_PROMPT },
42
+ { role: "user", content: userPrompt },
43
+ ],
44
+ model,
45
+ )
46
+
47
+ let parsed: unknown
48
+ try {
49
+ const cleaned = raw.replace(/^```(?:json)?\s*/m, "").replace(/\s*```$/m, "").trim()
50
+ parsed = JSON.parse(cleaned)
51
+ } catch {
52
+ throw new Error(`Advisor: LLM returned non-JSON. Raw (first 300 chars): ${raw.slice(0, 300)}`)
53
+ }
54
+
55
+ const result = AdvisorAnswerSchema.safeParse(parsed)
56
+ if (!result.success) {
57
+ throw new Error(`Advisor: LLM output failed validation. Issues: ${JSON.stringify(result.error.issues)}`)
58
+ }
59
+
60
+ return result.data
61
+ }
62
+
63
+ /**
64
+ * Ask the Advisor a plain-language question.
65
+ *
66
+ * Internally calls the LLM and validates the answer against a satisfaction
67
+ * threshold (confidence ≥ 0.7, no blockers). Retries up to MAX_RETRIES times
68
+ * with the previous answer included as context. Returns the best answer found
69
+ * within the retry budget regardless of whether the threshold was met.
70
+ *
71
+ * Throws if the LLM returns non-JSON or output that fails schema validation.
72
+ */
73
+ export async function ask(input: AdvisorInput, deps: AdvisorDeps): Promise<AdvisorOutput> {
74
+ let last: AdvisorAnswer | null = null
75
+
76
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
77
+ const answer = await callLLM(input, deps, attempt, last)
78
+ last = answer
79
+
80
+ const satisfied = answer.confidence >= SATISFACTION_THRESHOLD && answer.blockers.length === 0
81
+ if (satisfied || attempt === MAX_RETRIES) {
82
+ return { ...answer, question: input.question, retries: attempt }
83
+ }
84
+ }
85
+
86
+ return { ...last!, question: input.question, retries: MAX_RETRIES }
87
+ }
@@ -0,0 +1,2 @@
1
+ export { ask } from "./ask"
2
+ export type { AdvisorInput, AdvisorOutput, AdvisorDeps } from "./types"
@@ -0,0 +1,50 @@
1
+ import type { OracleResult } from "../shared/types"
2
+ import { entryText } from "../shared/types"
3
+
4
+ export const SYSTEM_PROMPT = `You are the Quorum Advisor — the plain-language interface to a team's collective knowledge.
5
+
6
+ You receive a question from a developer or engineering manager, along with relevant Chronicle evidence.
7
+ Synthesise that evidence into a clear, concise answer a human can act on.
8
+
9
+ Rules:
10
+ - Write for a human who does not know what "Chronicle entries" or "vector search" mean.
11
+ - Be direct. One clear recommendation, not a list of options unless genuinely necessary.
12
+ - If Chronicle has relevant evidence, reference it plainly: "the team already decided X".
13
+ - If Chronicle has no evidence, say so honestly — do not invent history.
14
+ - Blockers are hard blockers only — things that MUST be resolved before moving forward.
15
+ - risks are real concerns worth knowing, not theoretical edge cases.
16
+
17
+ Return ONLY valid JSON matching this schema (no markdown fences, no explanation):
18
+ {
19
+ "confidence": <number 0–1 — how confident are you given the available evidence>,
20
+ "what_we_know": <string — what Chronicle knows about this topic. Plain English, 1–3 sentences.>,
21
+ "risks": [<string — each real risk, plain English, one per item. Empty array if none.>],
22
+ "blockers": [<string — hard blockers only. Empty array if none.>],
23
+ "recommendation": <string — one clear recommended action>,
24
+ "next_step": <string — the specific next thing to do, e.g. a quorum command or a decision>
25
+ }`
26
+
27
+ export function formatEvidence(evidence: OracleResult[]): string {
28
+ if (evidence.length === 0) {
29
+ return "Chronicle has no prior entries on this topic."
30
+ }
31
+ return evidence
32
+ .map(e => {
33
+ const text = entryText(e)
34
+ const statusTag =
35
+ e.status === "refuted" ? " [REJECTED]" :
36
+ e.status === "validated" ? " [VALIDATED]" : ""
37
+ return `[${e.id.slice(0, 8)}]${statusTag} ${text}\n Areas: ${e.affected_areas.join(", ")}`
38
+ })
39
+ .join("\n\n")
40
+ }
41
+
42
+ export function buildUserPrompt(question: string, evidence: OracleResult[]): string {
43
+ return [
44
+ "## Question",
45
+ question,
46
+ "",
47
+ "## Chronicle Evidence",
48
+ formatEvidence(evidence),
49
+ ].join("\n")
50
+ }
@@ -0,0 +1,26 @@
1
+ import type { OracleResult, LLMProvider } from "../shared/types"
2
+
3
+ export interface AdvisorInput {
4
+ question: string
5
+ evidence: OracleResult[]
6
+ }
7
+
8
+ export interface AdvisorAnswer {
9
+ confidence: number
10
+ what_we_know: string
11
+ risks: string[]
12
+ blockers: string[]
13
+ recommendation: string
14
+ next_step: string
15
+ }
16
+
17
+ export interface AdvisorOutput extends AdvisorAnswer {
18
+ question: string
19
+ /** Number of retries taken before the answer met the satisfaction threshold. */
20
+ retries: number
21
+ }
22
+
23
+ export interface AdvisorDeps {
24
+ llm: LLMProvider
25
+ model?: string
26
+ }
package/modules/setup.ts CHANGED
@@ -5,10 +5,12 @@ import { xenovaEmbed, warmEmbedder } from "./oracle/adapters/xenova-embedder"
5
5
  import { createLanceDBStore } from "./oracle/adapters/lance-db"
6
6
  import { evaluate } from "./jury/evaluate"
7
7
  import { deliberate } from "./council/deliberate"
8
+ import { ask as advisorAsk } from "./advisor/ask"
8
9
  import type { LLMProvider, OracleClient } from "./shared/types"
9
10
  import { entryText } from "./shared/types"
10
11
  import type { JuryInput, JuryOutput, JuryDeps } from "./jury/types"
11
12
  import type { CouncilInput, CouncilOutput, CouncilDeps, CouncilModels } from "./council/types"
13
+ import type { AdvisorOutput } from "./advisor/types"
12
14
 
13
15
  export interface SetupOptions {
14
16
  /**
@@ -69,6 +71,14 @@ export interface Modules {
69
71
  deliberate: (
70
72
  input: Omit<CouncilInput, "jury_output"> & { jury_output: JuryOutput },
71
73
  ) => Promise<CouncilOutput>
74
+
75
+ /**
76
+ * Ask the Advisor a plain-language question.
77
+ * Queries Oracle automatically, synthesises Chronicle evidence into a
78
+ * human-readable answer, and retries internally until the answer meets
79
+ * the confidence threshold (≥ 0.7, no blockers) or the retry budget runs out.
80
+ */
81
+ ask: (question: string) => Promise<AdvisorOutput>
72
82
  }
73
83
 
74
84
  /**
@@ -150,5 +160,10 @@ export async function setup(options: SetupOptions): Promise<Modules> {
150
160
  oracle,
151
161
  models: models.council,
152
162
  }),
163
+
164
+ ask: async (question: string) => {
165
+ const evidence = await oracle.query(question)
166
+ return advisorAsk({ question, evidence }, { llm })
167
+ },
153
168
  }
154
169
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balpal4495/quorum",
3
- "version": "0.4.2",
3
+ "version": "2.0.0",
4
4
  "description": "Portable reasoning layer for agentic codebases — Oracle, Jury, Council, Sentinel",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,8 +22,10 @@
22
22
  "bin": {
23
23
  "quorum": "bin/quorum.js"
24
24
  },
25
+ "exports": {
26
+ ".": "./modules/setup.ts"
27
+ },
25
28
  "scripts": {
26
- "init": "node bin/init.js",
27
29
  "test": "vitest run modules/ evals/",
28
30
  "test:watch": "vitest modules/",
29
31
  "typecheck": "tsc --noEmit"