@sebastientang/llm-council 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sebastien Tang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,286 @@
1
+ # LLM Council
2
+
3
+ When a decision is too important for one model's opinion, convene a council.
4
+
5
+ LLM Council is a TypeScript library for structured multi-model deliberation. It orchestrates multiple LLM participants through debate rounds, then synthesizes their arguments into a single recommendation with confidence scores, risks, and validation gates.
6
+
7
+ ## The Problem
8
+
9
+ Single-model outputs have blind spots. Asking the same model twice gives you correlated errors, not independent verification. Multi-model voting is better, but loses the reasoning behind each vote.
10
+
11
+ LLM Council fixes this by running structured deliberation protocols where participants build cases, attack assumptions, advocate for alternatives, and narrate failure scenarios — then synthesizes the full debate into a calibrated recommendation.
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ npm install @sebastientang/llm-council
17
+ ```
18
+
19
+ ### Library
20
+
21
+ ```typescript
22
+ import {
23
+ Council,
24
+ AnthropicProvider,
25
+ AdversarialProtocol,
26
+ DialecticalSynthesizer,
27
+ PERSONAS,
28
+ } from '@sebastientang/llm-council'
29
+
30
+ const provider = new AnthropicProvider({
31
+ apiKey: process.env.ANTHROPIC_API_KEY!,
32
+ })
33
+
34
+ const council = new Council({
35
+ providers: new Map([['anthropic', provider]]),
36
+ protocol: new AdversarialProtocol(),
37
+ synthesizer: new DialecticalSynthesizer(),
38
+ })
39
+
40
+ const result = await council.deliberate({
41
+ topic: 'Should we build in-house or buy Salesforce?',
42
+ options: ['Build in-house CRM', 'Buy Salesforce'],
43
+ preferredOption: 'Build in-house CRM',
44
+ context: 'Team of 3 engineers. $50K annual budget. Need CRM in 3 months.',
45
+ participants: [
46
+ { ...PERSONAS.proposer, provider: 'anthropic', model: 'claude-sonnet-4-20250514' },
47
+ { ...PERSONAS.challenger, provider: 'anthropic', model: 'claude-sonnet-4-20250514' },
48
+ { ...PERSONAS.steelmanner, provider: 'anthropic', model: 'claude-sonnet-4-20250514' },
49
+ { ...PERSONAS.preMortem, provider: 'anthropic', model: 'claude-sonnet-4-20250514' },
50
+ ],
51
+ rounds: 2,
52
+ })
53
+
54
+ console.log(result.synthesis.recommendation)
55
+ // "Buy Salesforce — the 3-month timeline and 3-person team make in-house build high-risk."
56
+
57
+ console.log(result.synthesis.confidence)
58
+ // 78
59
+ ```
60
+
61
+ ### CLI
62
+
63
+ ```bash
64
+ # With Anthropic
65
+ ANTHROPIC_API_KEY=sk-... npx llm-council "Should we adopt microservices?"
66
+
67
+ # With OpenRouter (access to GPT-4o, Llama, Mixtral, etc.)
68
+ OPENROUTER_API_KEY=sk-... npx llm-council --provider openrouter "Should we adopt microservices?"
69
+
70
+ # With specific model and protocol
71
+ npx llm-council --provider openrouter --model openai/gpt-4o --protocol peer-review "topic"
72
+
73
+ # Full options
74
+ npx llm-council --help
75
+ ```
76
+
77
+ ## Protocols
78
+
79
+ ### Adversarial Protocol (default)
80
+
81
+ Inspired by Dialectical Inquiry (Mason 1969) and Pre-Mortem analysis (Klein 2007):
82
+
83
+ 1. **Round 1 — Independent Briefs**: Each participant writes their position without seeing others
84
+ - **Proposer**: Builds the strongest case for the preferred option
85
+ - **Challenger**: Red-teams the preferred option, finds vulnerabilities
86
+ - **Steelmanner**: Advocates for the rejected option at full strength
87
+ - **Pre-Mortem**: Assumes the preferred option failed, narrates how
88
+
89
+ 2. **Round 2 — Targeted Rebuttals**: Each participant reads others' briefs and responds
90
+
91
+ 3. **Synthesis**: An LLM reviews all briefs and rebuttals, producing a recommendation with confidence score, risks, dissent, validation gates, and assumptions.
92
+
93
+ ### Peer-Review Protocol
94
+
95
+ Karpathy-style anonymized ranking:
96
+
97
+ 1. **Round 1 — Independent Briefs**: Same as adversarial
98
+ 2. **Round 2 — Anonymized Ranking**: Each participant sees all briefs labeled A, B, C, D (including their own) and ranks them with justifications
99
+ 3. **Round 3 — Re-vote (optional)**: After seeing others' rankings, participants submit a final ranking
100
+
101
+ ```typescript
102
+ import { PeerReviewProtocol } from '@sebastientang/llm-council'
103
+
104
+ const council = new Council({
105
+ providers: new Map([['anthropic', provider]]),
106
+ protocol: new PeerReviewProtocol({ enableRevote: true }),
107
+ synthesizer: new ChairmanSynthesizer(),
108
+ })
109
+ ```
110
+
111
+ ## Synthesizers
112
+
113
+ ### Dialectical Synthesizer (default)
114
+
115
+ Merges all arguments into a new recommendation. Weighs evidence over opinion, favors reversibility when confidence is low, synthesizes rather than averages.
116
+
117
+ ### Chairman Synthesizer
118
+
119
+ Selects the best argument rather than creating a new synthesis. Evaluates responses on evidence quality, risk awareness, actionability, and logical coherence.
120
+
121
+ ```typescript
122
+ import { ChairmanSynthesizer } from '@sebastientang/llm-council'
123
+
124
+ const council = new Council({
125
+ providers: new Map([['anthropic', provider]]),
126
+ protocol: new AdversarialProtocol(),
127
+ synthesizer: new ChairmanSynthesizer({ temperature: 0.2 }),
128
+ })
129
+ ```
130
+
131
+ ## Providers
132
+
133
+ ### AnthropicProvider
134
+
135
+ Direct access to Claude models via the Anthropic API.
136
+
137
+ ```typescript
138
+ const provider = new AnthropicProvider({
139
+ apiKey: process.env.ANTHROPIC_API_KEY!,
140
+ defaultModel: 'claude-sonnet-4-20250514', // optional
141
+ defaultMaxTokens: 1024, // optional
142
+ })
143
+ ```
144
+
145
+ ### OpenRouterProvider
146
+
147
+ Access to 100+ models (GPT-4o, Llama, Mixtral, Gemini, etc.) through OpenRouter.
148
+
149
+ ```typescript
150
+ import { OpenRouterProvider } from '@sebastientang/llm-council'
151
+
152
+ const provider = new OpenRouterProvider({
153
+ apiKey: process.env.OPENROUTER_API_KEY!,
154
+ defaultModel: 'openai/gpt-4o', // optional
155
+ appName: 'my-app', // optional, shown in OpenRouter dashboard
156
+ })
157
+ ```
158
+
159
+ ## Architecture
160
+
161
+ ```
162
+ Council
163
+ ├── Provider (LLM API) → AnthropicProvider, OpenRouterProvider
164
+ ├── Protocol (rounds) → AdversarialProtocol, PeerReviewProtocol
165
+ └── Synthesizer (final call) → DialecticalSynthesizer, ChairmanSynthesizer
166
+ ```
167
+
168
+ Three extension points — implement the interface and plug in:
169
+
170
+ | Component | Interface | Built-in |
171
+ |-----------|-----------|----------|
172
+ | **Provider** | `LLMProvider` | `AnthropicProvider`, `OpenRouterProvider` |
173
+ | **Protocol** | `Protocol` | `AdversarialProtocol`, `PeerReviewProtocol` |
174
+ | **Synthesizer** | `Synthesizer` | `DialecticalSynthesizer`, `ChairmanSynthesizer` |
175
+
176
+ ## Events
177
+
178
+ Track progress during deliberation:
179
+
180
+ ```typescript
181
+ council.on('round:start', ({ round, participantCount }) => {
182
+ console.log(`Round ${round} starting with ${participantCount} participants`)
183
+ })
184
+
185
+ council.on('response', (message) => {
186
+ console.log(`${message.participantName} responded (${message.tokenCount.output} tokens)`)
187
+ })
188
+
189
+ council.on('synthesis:start', () => console.log('Synthesizing...'))
190
+ council.on('complete', (result) => console.log(`Done in ${result.metadata.durationMs}ms`))
191
+ council.on('error', (err) => console.error(err))
192
+ ```
193
+
194
+ ## Custom Personas
195
+
196
+ Use the built-in presets or define your own:
197
+
198
+ ```typescript
199
+ import { PERSONAS } from '@sebastientang/llm-council'
200
+
201
+ // Built-in: proposer, challenger, steelmanner, preMortem
202
+ const participant = {
203
+ ...PERSONAS.proposer,
204
+ provider: 'anthropic',
205
+ model: 'claude-sonnet-4-20250514',
206
+ }
207
+
208
+ // Custom persona
209
+ const financialAnalyst = {
210
+ id: 'financial-analyst',
211
+ name: 'Financial Analyst',
212
+ provider: 'anthropic',
213
+ model: 'claude-sonnet-4-20250514',
214
+ systemPrompt: 'You are a financial analyst. Evaluate decisions through ROI, cash flow, and opportunity cost. Always quantify.',
215
+ temperature: 0.5,
216
+ }
217
+ ```
218
+
219
+ ## Token Budget
220
+
221
+ Control costs with per-response and total token limits:
222
+
223
+ ```typescript
224
+ const result = await council.deliberate({
225
+ // ...
226
+ tokenBudget: {
227
+ perResponse: 512, // max tokens per participant response
228
+ total: 8000, // tracked in metadata (not enforced)
229
+ },
230
+ })
231
+
232
+ console.log(result.metadata.totalTokens)
233
+ // { input: 4200, output: 2800 }
234
+
235
+ console.log(result.metadata.modelBreakdown)
236
+ // { "anthropic/claude-sonnet-4-20250514": { input: 4200, output: 2800 } }
237
+ ```
238
+
239
+ ## API Reference
240
+
241
+ ### `Council`
242
+
243
+ | Method | Description |
244
+ |--------|-------------|
245
+ | `deliberate(config)` | Run a full deliberation, returns `DeliberationResult` |
246
+ | `on(event, handler)` | Subscribe to events |
247
+ | `off(event, handler)` | Unsubscribe from events |
248
+
249
+ ### `DeliberationConfig`
250
+
251
+ | Field | Type | Required | Description |
252
+ |-------|------|----------|-------------|
253
+ | `topic` | `string` | Yes | The decision or question |
254
+ | `options` | `string[]` | No | Explicit options to evaluate |
255
+ | `preferredOption` | `string` | No | Which way you're leaning |
256
+ | `context` | `string` | No | Background, constraints |
257
+ | `participants` | `Participant[]` | Yes | Min 2 participants |
258
+ | `rounds` | `number` | No | 1-5, default 2 |
259
+ | `tokenBudget` | `object` | No | Cost controls |
260
+
261
+ ### `Synthesis`
262
+
263
+ | Field | Type | Description |
264
+ |-------|------|-------------|
265
+ | `recommendation` | `string` | The synthesized recommendation |
266
+ | `confidence` | `number` | 0-100 confidence score |
267
+ | `reasoning` | `string` | Why this recommendation |
268
+ | `risks` | `string[]` | Top risks to monitor |
269
+ | `dissent` | `string[]` | Counter-arguments that survived |
270
+ | `validationGates` | `string[]` | Measurable checkpoints |
271
+ | `assumptions` | `string[]` | What must hold true |
272
+ | `raw` | `string` | Raw synthesis output |
273
+
274
+ ## Roadmap
275
+
276
+ - **v0.3**: Streaming responses, session persistence, web UI
277
+
278
+ ## Inspired By
279
+
280
+ - [karpathy/llm-council](https://github.com/karpathy/llm-council) — Multi-model deliberation concept
281
+ - Dialectical Inquiry (Mason 1969) — Thesis tested by antithesis
282
+ - Pre-Mortem (Klein 2007) — Assume failure, backcast to causes
283
+
284
+ ## License
285
+
286
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/cli.js'