profileShare

rasmusjy / roundtable

Read-only snapshot

No repository description.

main default branch 181 files Expires Sep 13, 2026, 9:06 AM
demo-fixtures.data.ts 29,093 bytes
1 /**
2 * The hand-written content for the seeded demo debates. Kept apart from the
3 * expansion logic in demo-fixtures.ts so the prose is easy to edit. No em-dashes
4 * or curly quotes here on purpose - plain ASCII throughout.
5 */
6 import type { FixtureSpec } from './demo-fixtures';
7
8 const monolith: FixtureSpec = {
9 question:
10 'Our 4-engineer startup is building a new B2B SaaS. Should we start with a modular monolith or microservices?',
11 models: ['openai/gpt-4o', 'anthropic/claude-3.5-sonnet', 'google/gemini-pro-1.5'],
12 chairmanModel: 'x-ai/grok-2-1212',
13 convergenceModel: 'google/gemini-2.0-flash-001',
14 convergenceThreshold: 85,
15 answers: [
16 // GPT-4o - pragmatic monolith
17 `Start with a modular monolith. With four engineers your scarcest resource is engineering attention, and microservices tax it heavily: network calls, distributed transactions, versioned APIs, and a dozen deploy pipelines before you have product-market fit. Build one deployable application, but enforce clean module boundaries - separate packages for billing, auth, and tenanting, with no cross-module database access. That keeps the door open to extracting a service later, the day a specific module actually needs independent scaling or a different runtime. Ship features now; buy the distributed-systems complexity only when a real bottleneck forces you to.`,
18 // Claude 3.5 Sonnet - nuanced, boundaries are the deliverable
19 `A modular monolith is the right default, but the value is in the discipline, not the deployment unit. The failure mode for a small team is not "monolith vs microservices", it is a big ball of mud either way. Define your bounded contexts up front - billing, identity, and the core product domain - give each an explicit interface, and forbid reaching across contexts except through those interfaces. Microservices force those boundaries with the network, at the price of operational overhead you cannot afford yet. So get the boundaries for free in a monolith, through module structure and code review. Revisit only when an organizational reason (a team that owns a context) or a scaling reason (a component with wildly different load) makes a separate deployable worth its cost.`,
20 // Gemini Pro 1.5 - argues for a couple of early services
21 `It depends on your deployment and scaling story more than on team size. If parts of your product have very different scaling or compliance profiles - a webhook ingester hammered by traffic, or a data-residency-sensitive component - a couple of well-chosen services from day one can be cheaper than retrofitting them later. I would avoid full microservices, but consider a "macroservice" split: the main app plus one or two services drawn along the sharpest scaling or isolation seams. Independent deploys also shrink blast radius, which matters when four people push to production daily. The key is to keep the count tiny and the boundaries obvious.`,
22 ],
23 rounds: [
24 {
25 critiques: [
26 {
27 by: 0,
28 on: [
29 {
30 p: 1,
31 score: 8,
32 strengths: ['Correctly identifies the ball-of-mud as the real risk', 'Frames boundaries as the actual deliverable'],
33 weaknesses: ['Does not give concrete triggers for when to extract a service'],
34 note: 'Strong on principle, light on the operational specifics a founder needs.',
35 },
36 {
37 p: 2,
38 score: 6,
39 strengths: ['Fair point that scaling and compliance seams can justify an early split'],
40 weaknesses: [
41 'Underweights the fixed cost of even one extra service for a team of four',
42 'Blast radius is better handled by tests and feature flags than by network boundaries at this stage',
43 ],
44 note: 'Reasonable exceptions, but the default it implies is too eager to split.',
45 },
46 ],
47 },
48 {
49 by: 1,
50 on: [
51 {
52 p: 0,
53 score: 8,
54 strengths: ['Concrete rule: no cross-module database access', 'Names the real cost drivers of microservices'],
55 weaknesses: ['Slightly understates how easily clean modules rot without enforcement'],
56 note: 'Actionable and correct. I would pair it with automated boundary checks.',
57 },
58 {
59 p: 2,
60 score: 6,
61 strengths: ['Independent deploys and isolation are genuine benefits'],
62 weaknesses: [
63 'Treats a couple of services as low-cost, which it is not at this size',
64 'Compliance and residency rarely bite a pre-PMF startup',
65 ],
66 note: 'The exceptions are valid but rare enough not to be the default.',
67 },
68 ],
69 },
70 {
71 by: 2,
72 on: [
73 {
74 p: 0,
75 score: 7,
76 strengths: ['Pragmatic and shippable', 'The extract-later path is sound'],
77 weaknesses: ['Assumes the future extraction will actually happen; teams often never pay down that debt'],
78 note: 'Good default, but extract later is easier said than done.',
79 },
80 {
81 p: 1,
82 score: 7,
83 strengths: ['Bounded-contexts framing is exactly right'],
84 weaknesses: ['Does not address cases where scaling profiles genuinely differ from day one'],
85 note: 'Principled, but a bit absolutist about avoiding all early services.',
86 },
87 ],
88 },
89 ],
90 revisions: [
91 {
92 p: 0,
93 changed: true,
94 content: `Start with a modular monolith with boundaries you actually enforce. One deployable application; each context (billing, identity, the core domain) lives in its own module with a public interface and its own database schema. Make the boundaries real, not aspirational: add an architecture test to CI that fails the build on cross-context imports or shared tables. Extract a context into its own service only when you hit a concrete trigger - it needs to scale independently, it needs a different runtime or language, it falls under a separate compliance boundary, or a dedicated team will own it. Until one of those is true, one deployable wins. This gives you most of what microservices promise without the operational tax a four-person team cannot afford before product-market fit.`,
95 summary: 'Added enforcement (CI architecture tests, per-context schemas) and explicit extraction triggers, answering the "clean modules rot" and "when to split" critiques.',
96 bullets: ['Automated architecture tests in CI', 'A schema per bounded context', 'Named, concrete triggers for extraction'],
97 },
98 {
99 p: 1,
100 changed: true,
101 content: `A modular monolith is the right default, and here is the concrete version. One deployable, with bounded contexts as modules - billing, identity, core domain - each exposing an explicit interface and owning its own tables, enforced both in code review and by an automated boundary check. Extract a context into a service when there is an organizational reason (a team will own it) or a technical one (its load or runtime diverges sharply from the rest). Structuring it this way keeps the boundaries honest now, and makes the eventual extraction mechanical rather than a rewrite.`,
102 summary: 'Made the principle concrete with explicit extraction triggers and named enforcement, addressing the "too abstract" critique. Converged with the enforced-boundaries view.',
103 bullets: ['Added explicit extraction triggers', 'Named the enforcement mechanisms'],
104 },
105 {
106 p: 2,
107 changed: true,
108 content: `I will narrow my position. For a four-person, pre-PMF team, start with a modular monolith and enforced boundaries; the fixed cost of even one extra service usually is not worth it, and blast radius is better handled with tests and feature flags at this stage. I would keep one exception: if a component has a genuinely different and punishing scaling profile on day one - a high-volume ingester - or a hard data-residency requirement, splitting just that one component out can be cheaper than retrofitting it later. Otherwise: monolith now, extract on a concrete trigger.`,
109 summary: 'Conceded that a couple of early services is too eager as a default for this team size. Kept a single narrow exception for a day-one extreme-scaling or residency seam.',
110 bullets: ['Dropped the macroservice-by-default stance', 'Kept one narrow early-split exception'],
111 },
112 ],
113 convergence: {
114 score: 74,
115 disagreements: [
116 {
117 topic: 'Day-one exceptions',
118 summary: 'Whether any single component justifies its own service before product-market fit.',
119 positions: [
120 { p: 2, stance: 'One narrow exception (extreme scaling or data residency) can justify an early split.' },
121 { p: 0, stance: 'Even those are better served by a well-isolated module now, extracted on a real trigger.' },
122 ],
123 },
124 ],
125 },
126 },
127 {
128 critiques: [
129 {
130 by: 0,
131 on: [
132 { p: 1, score: 9, strengths: ['Now fully concrete: triggers plus enforcement'], weaknesses: ['Could name the specific architecture-test tooling'], note: 'Essentially aligned. Excellent.' },
133 { p: 2, score: 8, strengths: ['Good concession; the narrow exception is defensible'], weaknesses: ['The exception risks becoming a slippery slope without a hard bar'], note: 'Agree, provided the exception stays truly exceptional.' },
134 ],
135 },
136 {
137 by: 1,
138 on: [
139 { p: 0, score: 9, strengths: ['CI boundary checks and per-context schemas make it real'], weaknesses: ['Nothing material'], note: 'The strongest, most actionable version.' },
140 { p: 2, score: 8, strengths: ['Reasonable, well-narrowed position'], weaknesses: ['Would require the day-one split to clear a very high bar'], note: 'Close to consensus.' },
141 ],
142 },
143 {
144 by: 2,
145 on: [
146 { p: 0, score: 9, strengths: ['Triggers plus enforcement answer my worry that extraction never happens'], weaknesses: ['Still assumes the team keeps the CI checks green'], note: 'I am largely convinced.' },
147 { p: 1, score: 8, strengths: ['Bounded contexts plus triggers'], weaknesses: ['Slightly less concrete than the tooling in the other answer'], note: 'Aligned.' },
148 ],
149 },
150 ],
151 revisions: [
152 {
153 p: 0,
154 changed: false,
155 content: `Start with a modular monolith with boundaries you actually enforce. One deployable application; each context (billing, identity, the core domain) lives in its own module with a public interface and its own database schema. Make the boundaries real, not aspirational: add an architecture test to CI that fails the build on cross-context imports or shared tables. Extract a context into its own service only when you hit a concrete trigger - it needs to scale independently, it needs a different runtime or language, it falls under a separate compliance boundary, or a dedicated team will own it. Until one of those is true, one deployable wins. This gives you most of what microservices promise without the operational tax a four-person team cannot afford before product-market fit.`,
156 summary: 'No substantive change; the position held up under critique. Implementation note: enforce boundaries with a tool like import-linter (Python) or ArchUnit (JVM).',
157 bullets: [],
158 },
159 {
160 p: 1,
161 changed: false,
162 content: `A modular monolith is the right default, and here is the concrete version. One deployable, with bounded contexts as modules - billing, identity, core domain - each exposing an explicit interface and owning its own tables, enforced both in code review and by an automated boundary check. Extract a context into a service when there is an organizational reason (a team will own it) or a technical one (its load or runtime diverges sharply from the rest). Structuring it this way keeps the boundaries honest now, and makes the eventual extraction mechanical rather than a rewrite.`,
163 summary: 'Defending the revised position; it converged with the council and needs no further change.',
164 bullets: [],
165 },
166 {
167 p: 2,
168 changed: true,
169 content: `I will narrow my position. For a four-person, pre-PMF team, start with a modular monolith and enforced boundaries; the fixed cost of even one extra service usually is not worth it, and blast radius is better handled with tests and feature flags at this stage. For the day-one exception, set a hard bar: only pre-split a component if you can point to a specific, quantified scaling or legal requirement that a module provably cannot satisfy - otherwise it stays in the monolith. So: monolith now, one deployable, extract on a concrete trigger.`,
170 summary: 'Added a hard, quantified bar for the early-split exception so it cannot become a slippery slope, addressing the remaining critique.',
171 bullets: ['Required a specific, quantified justification for any day-one split'],
172 },
173 ],
174 convergence: {
175 score: 91,
176 disagreements: [],
177 },
178 },
179 ],
180 finalAnswer: `Start with a modular monolith - one deployable application - but treat its internal boundaries as first-class. Split the codebase into bounded contexts (billing, identity, and your core product domain), give each an explicit public interface and its own database schema, and enforce those boundaries automatically with an architecture test in CI that fails on cross-context imports or shared tables. This buys you almost everything microservices promise - clear ownership, replaceable parts, contained blast radius - without the operational tax a four-person team cannot afford before product-market fit: multiple deploy pipelines, network failure modes, distributed transactions, and versioned inter-service APIs.
181
182 Extract a context into its own service only when you hit a concrete, nameable trigger: it needs to scale independently, it requires a different runtime or language, it falls under a separate compliance or data-residency boundary, or a dedicated team will own it. Because the boundaries were enforced from the start, that extraction is mechanical rather than a rewrite. In short: get the discipline of microservices now, effectively for free, through module structure, and pay for their operations only when a real constraint - not a hypothetical one - demands it.`,
183 dissent: [
184 {
185 topic: 'Splitting a service on day one',
186 positions: [
187 { p: 2, text: 'Would allow one narrow exception: a component with an extreme, quantified scaling or data-residency requirement can justify its own service from the start.' },
188 { p: 0, text: 'Even those cases are better served by a well-isolated module initially, extracted the moment the requirement is proven, avoiding any premature distribution.' },
189 ],
190 },
191 ],
192 provenance: [
193 {
194 text: 'Start with one deployable application, split into bounded contexts (billing, identity, core domain) behind explicit interfaces.',
195 supportedBy: [0, 1],
196 contestedBy: [2],
197 },
198 {
199 text: 'Enforce module boundaries automatically with an architecture test in CI that fails on cross-context imports or shared tables.',
200 supportedBy: [0, 1],
201 },
202 {
203 text: 'Extract a context into its own service only on a concrete trigger: independent scaling, a different runtime, a compliance boundary, or dedicated ownership.',
204 supportedBy: [0, 1, 2],
205 },
206 {
207 text: 'Because the boundaries were enforced from the start, a later extraction is mechanical rather than a rewrite.',
208 supportedBy: [],
209 },
210 ],
211 };
212
213 const restVsGraphql: FixtureSpec = {
214 question:
215 'For a new internal service API used by our web and mobile apps, should we go with REST or GraphQL?',
216 models: ['openai/gpt-4o', 'anthropic/claude-3.5-sonnet', 'deepseek/deepseek-chat'],
217 chairmanModel: 'google/gemini-pro-1.5',
218 convergenceModel: 'google/gemini-2.0-flash-001',
219 convergenceThreshold: 85,
220 answers: [
221 // GPT-4o - REST default
222 `Default to REST. For an internal API with two first-party clients, REST over HTTP with clear resource nouns is boring in the best way: cacheable with standard HTTP semantics, trivial to debug with curl, and supported by every proxy and tool you already run. GraphQL earns its keep when you have many heterogeneous clients that each need different slices of data and you want to avoid endpoint sprawl - that is not you yet. Start REST; if the mobile team starts complaining about over-fetching or too many round-trips, add a GraphQL or backend-for-frontend layer for that client specifically.`,
223 // Claude 3.5 Sonnet - depends on data + clients
224 `The honest answer is that it depends on your data and your clients, but for two first-party apps I would lean REST with a caveat. If your screens map cleanly to resources, REST is simpler to build, cache, and secure. GraphQL's real win is letting a client select exactly the fields it needs across a graph of related entities in a single request - valuable if your mobile app is on flaky networks and your data is highly relational. Its costs are real too: you own query-complexity limiting, caching gets harder, and authorization moves into resolvers. For most internal APIs, REST plus a few purpose-built aggregate endpoints gets you most of GraphQL's benefit at a fraction of the cost.`,
225 // DeepSeek - GraphQL for velocity
226 `I would choose GraphQL. With a web and a mobile client evolving in parallel, a single typed schema becomes a shared contract: clients fetch exactly the fields they need, the backend stops shipping a bespoke endpoint for every screen, and the type system plus introspection give you strong tooling and code generation. Over-fetching and under-fetching both disappear. Yes, you take on caching and query-cost management, but those are well-trodden problems with mature libraries. The developer velocity from not negotiating a new endpoint for every UI change is worth the price.`,
227 ],
228 rounds: [
229 {
230 critiques: [
231 {
232 by: 0,
233 on: [
234 { p: 1, score: 8, strengths: ['REST plus aggregate endpoints captures most of the upside cheaply'], weaknesses: ['Does not say when the relational or flaky-network case actually tips to GraphQL'], note: 'Balanced and practical.' },
235 { p: 2, score: 6, strengths: ['Right that a shared typed schema speeds client work'], weaknesses: ['Understates caching and authorization cost for a small team', 'Calling those solved problems hand-waves a real operational burden'], note: 'Overweights velocity, underweights ops.' },
236 ],
237 },
238 {
239 by: 1,
240 on: [
241 { p: 0, score: 8, strengths: ['Correct default, with a good tie-break: add a BFF per client if over-fetching bites'], weaknesses: ['Could acknowledge relational data as a genuine GraphQL trigger'], note: 'Solid, if slightly REST-absolutist.' },
242 { p: 2, score: 6, strengths: ['The velocity argument is real when UIs change fast'], weaknesses: ['Caching and authorization costs are dismissed too quickly for a small team'], note: 'Good upside case, weak on the downside.' },
243 ],
244 },
245 {
246 by: 2,
247 on: [
248 { p: 0, score: 7, strengths: ['Pragmatic and low-risk'], weaknesses: ['Add GraphQL later underestimates the migration cost once REST endpoints proliferate'], note: 'Safe, but may defer a decision that only gets costlier.' },
249 { p: 1, score: 8, strengths: ['Names the exact conditions where GraphQL wins'], weaknesses: ['The aggregate-endpoints path can sprawl too'], note: 'Closest to how I would actually decide.' },
250 ],
251 },
252 ],
253 revisions: [
254 {
255 p: 0,
256 changed: true,
257 content: `Default to REST for two first-party clients whose screens map cleanly to resources - it is cheaper to build, cache, secure, and debug. But check two things now, because they are the triggers that tip the balance toward GraphQL: (1) highly relational data where clients routinely need nested, varying slices, and (2) fast-diverging client needs that would otherwise breed a new endpoint per screen. If neither holds, REST plus a handful of aggregate or backend-for-frontend endpoints is the cheaper path. If both hold strongly, start with GraphQL to avoid a costly migration later.`,
258 summary: 'Added explicit triggers that would justify GraphQL up front, answering the "when does it tip" critique.',
259 bullets: ['Named the relational-data and diverging-clients triggers'],
260 },
261 {
262 p: 1,
263 changed: false,
264 content: `The honest answer is that it depends on your data and your clients, but for two first-party apps I would lean REST with a caveat. If your screens map cleanly to resources, REST is simpler to build, cache, and secure. GraphQL's real win is letting a client select exactly the fields it needs across a graph of related entities in a single request - valuable if your mobile app is on flaky networks and your data is highly relational. Its costs are real too: you own query-complexity limiting, caching gets harder, and authorization moves into resolvers. For most internal APIs, REST plus a few purpose-built aggregate endpoints gets you most of GraphQL's benefit at a fraction of the cost - just put light governance on those endpoints so they do not sprawl.`,
265 summary: 'Position held; it already scoped the trade-off. Added that aggregate endpoints need governance to avoid their own sprawl.',
266 bullets: [],
267 },
268 {
269 p: 2,
270 changed: true,
271 content: `I will qualify my answer. GraphQL is the better fit when data is relational and client needs diverge quickly - common, but not universal. For a small team whose screens map cleanly to resources, REST is genuinely cheaper to operate, and GraphQL's caching and authorization costs are real, not trivial. So: choose GraphQL if you expect many evolving views over connected data; otherwise start REST and add a GraphQL or BFF layer for the client that needs it. The decision should follow the data shape and the rate of UI change, not fashion.`,
272 summary: 'Conceded that GraphQL\'s operational costs are real, and made the choice conditional on data shape and UI churn rather than a blanket recommendation.',
273 bullets: ['Made GraphQL conditional on relational data and fast-changing UIs', 'Acknowledged the caching and authorization costs'],
274 },
275 ],
276 convergence: {
277 score: 79,
278 disagreements: [
279 {
280 topic: 'The default choice',
281 summary: 'Whether to start REST-by-default or GraphQL-by-default for two first-party clients.',
282 positions: [
283 { p: 0, stance: 'REST by default; adopt GraphQL only on clear triggers.' },
284 { p: 2, stance: 'GraphQL by default when UIs change fast over connected data.' },
285 ],
286 },
287 ],
288 },
289 },
290 {
291 critiques: [
292 {
293 by: 0,
294 on: [
295 { p: 1, score: 9, strengths: ['Clear decision criteria, no dogma'], weaknesses: ['Nothing material'], note: 'The best framing of the three.' },
296 { p: 2, score: 8, strengths: ['Good concession; now criteria-driven'], weaknesses: ['Still leans GraphQL in genuinely ambiguous cases'], note: 'Nearly aligned.' },
297 ],
298 },
299 {
300 by: 1,
301 on: [
302 { p: 0, score: 9, strengths: ['The triggers make the default actionable'], weaknesses: ['Nothing material'], note: 'Strong.' },
303 { p: 2, score: 8, strengths: ['The conditional recommendation is right'], weaknesses: ['Would set a higher bar before starting with GraphQL'], note: 'Close.' },
304 ],
305 },
306 {
307 by: 2,
308 on: [
309 { p: 0, score: 8, strengths: ['Low-risk and clear'], weaknesses: ['Migration cost if the triggers appear later is still a real risk'], note: 'Fair.' },
310 { p: 1, score: 9, strengths: ['Exactly the decision procedure I would use now'], weaknesses: ['Aggregate-endpoint governance is the one open detail'], note: 'Agreed.' },
311 ],
312 },
313 ],
314 revisions: [
315 {
316 p: 0,
317 changed: false,
318 content: `Default to REST for two first-party clients whose screens map cleanly to resources - it is cheaper to build, cache, secure, and debug. But check two things now, because they are the triggers that tip the balance toward GraphQL: (1) highly relational data where clients routinely need nested, varying slices, and (2) fast-diverging client needs that would otherwise breed a new endpoint per screen. If neither holds, REST plus a handful of aggregate or backend-for-frontend endpoints is the cheaper path. If both hold strongly, start with GraphQL to avoid a costly migration later.`,
319 summary: 'No change; the criteria held up under critique.',
320 bullets: [],
321 },
322 {
323 p: 1,
324 changed: false,
325 content: `The honest answer is that it depends on your data and your clients, but for two first-party apps I would lean REST with a caveat. If your screens map cleanly to resources, REST is simpler to build, cache, and secure. GraphQL's real win is letting a client select exactly the fields it needs across a graph of related entities in a single request - valuable if your mobile app is on flaky networks and your data is highly relational. Its costs are real too: you own query-complexity limiting, caching gets harder, and authorization moves into resolvers. For most internal APIs, REST plus a few purpose-built aggregate endpoints gets you most of GraphQL's benefit at a fraction of the cost - just put light governance on those endpoints so they do not sprawl.`,
326 summary: 'Defending the position; the council converged on a criteria-based choice.',
327 bullets: [],
328 },
329 {
330 p: 2,
331 changed: false,
332 content: `I will qualify my answer. GraphQL is the better fit when data is relational and client needs diverge quickly - common, but not universal. For a small team whose screens map cleanly to resources, REST is genuinely cheaper to operate, and GraphQL's caching and authorization costs are real, not trivial. So: choose GraphQL if you expect many evolving views over connected data; otherwise start REST and add a GraphQL or BFF layer for the client that needs it. The decision should follow the data shape and the rate of UI change, not fashion.`,
333 summary: 'No further change; my qualified, data-shape-driven position matches the emerging consensus.',
334 bullets: [],
335 },
336 ],
337 convergence: {
338 score: 90,
339 disagreements: [],
340 },
341 },
342 ],
343 finalAnswer: `Default to REST, and reach for GraphQL only on specific triggers. For an internal API serving two first-party clients whose screens map cleanly to resources, REST over HTTP is cheaper to build, cache, secure, and debug - you inherit standard HTTP semantics and every tool in your stack already speaks it. Cover the common over-fetching complaints with a small number of purpose-built aggregate or backend-for-frontend endpoints, and put light governance on them so they do not sprawl.
344
345 Choose GraphQL up front instead when two conditions hold together: your data is highly relational and clients routinely need nested, varying slices of it, and your web and mobile UIs change fast enough that REST would breed a new endpoint per screen. In that world a single typed schema pays for its operational costs - which are real: you must own query-complexity limiting, caching, and resolver-level authorization. Let the data shape and the rate of UI change decide, not fashion. The two clients you have today do not force GraphQL, but a growing, connected data model and fast-diverging clients would.`,
346 dissent: [
347 {
348 topic: 'Where the default should sit',
349 positions: [
350 { p: 2, text: 'Would set GraphQL as the default whenever UIs are expected to change quickly over connected data, accepting the operational cost for the velocity.' },
351 { p: 0, text: 'Keeps REST as the default until the relational-data and diverging-client triggers are clearly met, to avoid paying GraphQL\'s operational tax prematurely.' },
352 ],
353 },
354 ],
355 };
356
357 export const FIXTURES: FixtureSpec[] = [monolith, restVsGraphql];
358