dialectic 0.1.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.
Files changed (119) hide show
  1. package/.cursor/commands/setup-test.mdc +175 -0
  2. package/.cursor/rules/basic-code-cleanup.mdc +1110 -0
  3. package/.cursor/rules/riper5.mdc +96 -0
  4. package/.env.example +6 -0
  5. package/AGENTS.md +1052 -0
  6. package/LICENSE +21 -0
  7. package/README.md +93 -0
  8. package/WARP.md +113 -0
  9. package/dialectic-1.0.0.tgz +0 -0
  10. package/dialectic.js +10 -0
  11. package/docs/commands.md +375 -0
  12. package/docs/configuration.md +882 -0
  13. package/docs/context_summarization.md +1023 -0
  14. package/docs/debate_flow.md +1127 -0
  15. package/docs/eval_flow.md +795 -0
  16. package/docs/evaluator.md +141 -0
  17. package/examples/debate-config-openrouter.json +48 -0
  18. package/examples/debate_config1.json +48 -0
  19. package/examples/eval/eval1/eval_config1.json +13 -0
  20. package/examples/eval/eval1/result1.json +62 -0
  21. package/examples/eval/eval1/result2.json +97 -0
  22. package/examples/eval_summary_format.md +11 -0
  23. package/examples/example3/debate-config.json +64 -0
  24. package/examples/example3/eval_config2.json +25 -0
  25. package/examples/example3/problem.md +17 -0
  26. package/examples/example3/rounds_test/eval_run.sh +16 -0
  27. package/examples/example3/rounds_test/run_test.sh +16 -0
  28. package/examples/kata1/architect-only-solution_2-rounds.json +121 -0
  29. package/examples/kata1/architect-perf-solution_2-rounds.json +234 -0
  30. package/examples/kata1/debate-config-kata1.json +54 -0
  31. package/examples/kata1/eval_architect-only_2-rounds.json +97 -0
  32. package/examples/kata1/eval_architect-perf_2-rounds.json +97 -0
  33. package/examples/kata1/kata1-report.md +12224 -0
  34. package/examples/kata1/kata1-report_temps-01_01_01_07.md +2451 -0
  35. package/examples/kata1/kata1.md +5 -0
  36. package/examples/kata1/meta.txt +1 -0
  37. package/examples/kata2/debate-config.json +54 -0
  38. package/examples/kata2/eval_config1.json +21 -0
  39. package/examples/kata2/eval_config2.json +25 -0
  40. package/examples/kata2/kata2.md +5 -0
  41. package/examples/kata2/only_architect/debate-config.json +45 -0
  42. package/examples/kata2/only_architect/eval_run.sh +11 -0
  43. package/examples/kata2/only_architect/run_test.sh +5 -0
  44. package/examples/kata2/rounds_test/eval_run.sh +11 -0
  45. package/examples/kata2/rounds_test/run_test.sh +5 -0
  46. package/examples/kata2/summary_length_test/eval_run.sh +11 -0
  47. package/examples/kata2/summary_length_test/eval_run_w_clarify.sh +7 -0
  48. package/examples/kata2/summary_length_test/run_test.sh +5 -0
  49. package/examples/task-queue/debate-config.json +76 -0
  50. package/examples/task-queue/debate_report.md +566 -0
  51. package/examples/task-queue/task-queue-system.md +25 -0
  52. package/jest.config.ts +13 -0
  53. package/multi_agent_debate_spec.md +2980 -0
  54. package/package.json +38 -0
  55. package/sanity-check-problem.txt +9 -0
  56. package/src/agents/prompts/architect-prompts.ts +203 -0
  57. package/src/agents/prompts/generalist-prompts.ts +157 -0
  58. package/src/agents/prompts/index.ts +41 -0
  59. package/src/agents/prompts/judge-prompts.ts +19 -0
  60. package/src/agents/prompts/kiss-prompts.ts +230 -0
  61. package/src/agents/prompts/performance-prompts.ts +142 -0
  62. package/src/agents/prompts/prompt-types.ts +68 -0
  63. package/src/agents/prompts/security-prompts.ts +149 -0
  64. package/src/agents/prompts/shared.ts +144 -0
  65. package/src/agents/prompts/testing-prompts.ts +149 -0
  66. package/src/agents/role-based-agent.ts +386 -0
  67. package/src/cli/commands/debate.ts +761 -0
  68. package/src/cli/commands/eval.ts +475 -0
  69. package/src/cli/commands/report.ts +265 -0
  70. package/src/cli/index.ts +79 -0
  71. package/src/core/agent.ts +198 -0
  72. package/src/core/clarifications.ts +34 -0
  73. package/src/core/judge.ts +257 -0
  74. package/src/core/orchestrator.ts +432 -0
  75. package/src/core/state-manager.ts +322 -0
  76. package/src/eval/evaluator-agent.ts +130 -0
  77. package/src/eval/prompts/system.md +41 -0
  78. package/src/eval/prompts/user.md +64 -0
  79. package/src/providers/llm-provider.ts +25 -0
  80. package/src/providers/openai-provider.ts +84 -0
  81. package/src/providers/openrouter-provider.ts +122 -0
  82. package/src/providers/provider-factory.ts +64 -0
  83. package/src/types/agent.types.ts +141 -0
  84. package/src/types/config.types.ts +47 -0
  85. package/src/types/debate.types.ts +237 -0
  86. package/src/types/eval.types.ts +85 -0
  87. package/src/utils/common.ts +104 -0
  88. package/src/utils/context-formatter.ts +102 -0
  89. package/src/utils/context-summarizer.ts +143 -0
  90. package/src/utils/env-loader.ts +46 -0
  91. package/src/utils/exit-codes.ts +5 -0
  92. package/src/utils/id.ts +11 -0
  93. package/src/utils/logger.ts +48 -0
  94. package/src/utils/paths.ts +10 -0
  95. package/src/utils/progress-ui.ts +313 -0
  96. package/src/utils/prompt-loader.ts +79 -0
  97. package/src/utils/report-generator.ts +301 -0
  98. package/tests/clarifications.spec.ts +128 -0
  99. package/tests/cli.debate.spec.ts +144 -0
  100. package/tests/config-loading.spec.ts +206 -0
  101. package/tests/context-summarizer.spec.ts +131 -0
  102. package/tests/debate-config-custom.json +38 -0
  103. package/tests/env-loader.spec.ts +149 -0
  104. package/tests/eval.command.spec.ts +1191 -0
  105. package/tests/logger.spec.ts +19 -0
  106. package/tests/openai-provider.spec.ts +26 -0
  107. package/tests/openrouter-provider.spec.ts +279 -0
  108. package/tests/orchestrator-summary.spec.ts +386 -0
  109. package/tests/orchestrator.spec.ts +207 -0
  110. package/tests/prompt-loader.spec.ts +52 -0
  111. package/tests/prompts/architect.md +16 -0
  112. package/tests/provider-factory.spec.ts +150 -0
  113. package/tests/report.command.spec.ts +546 -0
  114. package/tests/role-based-agent-summary.spec.ts +476 -0
  115. package/tests/security-agent.spec.ts +221 -0
  116. package/tests/shared-prompts.spec.ts +318 -0
  117. package/tests/state-manager.spec.ts +251 -0
  118. package/tests/summary-prompts.spec.ts +153 -0
  119. package/tsconfig.json +49 -0
@@ -0,0 +1,265 @@
1
+ import { Command } from 'commander';
2
+ import { EXIT_INVALID_ARGS, EXIT_GENERAL_ERROR } from '../../utils/exit-codes';
3
+ import { infoUser, writeStderr } from '../index';
4
+ import { loadConfig } from './debate';
5
+ import { SystemConfig } from '../../types/config.types';
6
+ import { DebateState } from '../../types/debate.types';
7
+ import { AgentConfig, AGENT_ROLES, LLM_PROVIDERS } from '../../types/agent.types';
8
+ import { generateDebateReport } from '../../utils/report-generator';
9
+ import { createValidationError, readJsonFile, writeFileWithDirectories } from '../../utils/common';
10
+
11
+ // Error message constants
12
+ const ERROR_INVALID_DEBATE_JSON = 'Invalid debate JSON';
13
+ const ERROR_MISSING_FIELD = 'missing or invalid';
14
+ const ERROR_MISSING_ID_FIELD = `${ERROR_INVALID_DEBATE_JSON}: ${ERROR_MISSING_FIELD} id field`;
15
+ const ERROR_MISSING_PROBLEM_FIELD = `${ERROR_INVALID_DEBATE_JSON}: ${ERROR_MISSING_FIELD} problem field`;
16
+ const ERROR_MISSING_STATUS_FIELD = `${ERROR_INVALID_DEBATE_JSON}: ${ERROR_MISSING_FIELD} status field`;
17
+ const ERROR_MISSING_ROUNDS_FIELD = `${ERROR_INVALID_DEBATE_JSON}: ${ERROR_MISSING_FIELD} rounds array`;
18
+
19
+ /**
20
+ * Revives Date objects in a debate state that were serialized as strings.
21
+ *
22
+ * @param debateState - The debate state to revive dates in.
23
+ */
24
+ function reviveDatesInDebateState(debateState: DebateState): void {
25
+ // Revive top-level dates
26
+ if (debateState.createdAt && typeof debateState.createdAt === 'string') {
27
+ debateState.createdAt = new Date(debateState.createdAt);
28
+ }
29
+ if (debateState.updatedAt && typeof debateState.updatedAt === 'string') {
30
+ debateState.updatedAt = new Date(debateState.updatedAt);
31
+ }
32
+
33
+ // Revive round timestamps
34
+ for (const round of debateState.rounds) {
35
+ if (round.timestamp && typeof round.timestamp === 'string') {
36
+ round.timestamp = new Date(round.timestamp);
37
+ }
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Loads and validates a debate state from a JSON file.
43
+ *
44
+ * @param debatePath - Path to the debate JSON file.
45
+ * @returns The loaded and validated DebateState.
46
+ * @throws {Error} If the file doesn't exist, is invalid JSON, or lacks required fields.
47
+ */
48
+ function loadAndValidateDebateState(debatePath: string): DebateState {
49
+ const debate: DebateState = readJsonFile<DebateState>(debatePath, 'Debate file');
50
+
51
+ // Validate required fields
52
+ if (!debate.id || typeof debate.id !== 'string') {
53
+ throw createValidationError(ERROR_MISSING_ID_FIELD, EXIT_INVALID_ARGS);
54
+ }
55
+ if (!debate.problem || typeof debate.problem !== 'string') {
56
+ throw createValidationError(ERROR_MISSING_PROBLEM_FIELD, EXIT_INVALID_ARGS);
57
+ }
58
+ if (!debate.status || typeof debate.status !== 'string') {
59
+ throw createValidationError(ERROR_MISSING_STATUS_FIELD, EXIT_INVALID_ARGS);
60
+ }
61
+ if (!Array.isArray(debate.rounds)) {
62
+ throw createValidationError(ERROR_MISSING_ROUNDS_FIELD, EXIT_INVALID_ARGS);
63
+ }
64
+
65
+ // Revive Date objects if they were serialized as strings
66
+ reviveDatesInDebateState(debate);
67
+
68
+ return debate;
69
+ }
70
+
71
+ /**
72
+ * Extracts unique agent IDs and roles from the debate state by examining all contributions.
73
+ *
74
+ * @param debateState - The debate state to extract agent IDs from.
75
+ * @returns Map of agent IDs to their roles.
76
+ */
77
+ function extractAgentInfoFromDebate(debateState: DebateState): Map<string, string> {
78
+ const agentInfo = new Map<string, string>();
79
+
80
+ for (const round of debateState.rounds) {
81
+ for (const contribution of round.contributions) {
82
+ if (contribution.agentId && contribution.agentRole) {
83
+ // Only set if not already set (first occurrence wins)
84
+ if (!agentInfo.has(contribution.agentId)) {
85
+ agentInfo.set(contribution.agentId, contribution.agentRole);
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ return agentInfo;
92
+ }
93
+
94
+ /**
95
+ * Creates minimal agent configurations from debate state when config file is not provided.
96
+ *
97
+ * @param debateState - The debate state to extract agent information from.
98
+ * @returns Array of minimal agent configurations.
99
+ */
100
+ function createMinimalAgentConfigsFromDebate(debateState: DebateState): AgentConfig[] {
101
+ const agentInfo = extractAgentInfoFromDebate(debateState);
102
+ const agentConfigs: AgentConfig[] = [];
103
+
104
+ for (const [agentId, role] of agentInfo.entries()) {
105
+ // Validate role is a valid AgentRole, default to 'architect' if invalid
106
+ const validRoleValues = Object.values(AGENT_ROLES) as string[];
107
+ const validRole = validRoleValues.includes(role)
108
+ ? (role as typeof AGENT_ROLES[keyof typeof AGENT_ROLES])
109
+ : AGENT_ROLES.ARCHITECT;
110
+
111
+ // Create minimal config with only required fields
112
+ agentConfigs.push({
113
+ id: agentId,
114
+ name: agentId, // Use ID as name fallback
115
+ role: validRole,
116
+ model: 'N/A',
117
+ provider: LLM_PROVIDERS.OPENAI,
118
+ temperature: 0.5 // Default temperature
119
+ });
120
+ }
121
+
122
+ return agentConfigs;
123
+ }
124
+
125
+ /**
126
+ * Creates a minimal judge configuration when config file is not provided.
127
+ *
128
+ * @param debateState - The debate state (may contain judge info in finalSolution).
129
+ * @returns Minimal judge configuration.
130
+ */
131
+ function createMinimalJudgeConfigFromDebate(debateState: DebateState): AgentConfig {
132
+ // Extract judge ID from finalSolution if available
133
+ const judgeId = debateState.finalSolution?.synthesizedBy || 'judge-main';
134
+
135
+ return {
136
+ id: judgeId,
137
+ name: judgeId,
138
+ role: AGENT_ROLES.GENERALIST,
139
+ model: 'N/A',
140
+ provider: LLM_PROVIDERS.OPENAI,
141
+ temperature: 0.3 // Default judge temperature
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Matches agent configurations from the config file with agent IDs found in the debate state.
147
+ *
148
+ * @param sysConfig - The system configuration containing agent configs.
149
+ * @param agentIds - Array of agent IDs found in the debate state.
150
+ * @returns Array of agent configurations that match the agent IDs in the debate.
151
+ */
152
+ function matchAgentConfigsFromSysConfig(sysConfig: SystemConfig, agentIds: string[]): AgentConfig[] {
153
+ return agentIds
154
+ .map((agentId) => sysConfig.agents.find((a) => a.id === agentId))
155
+ .filter((config): config is AgentConfig => config !== undefined);
156
+ }
157
+
158
+ /**
159
+ * Writes the report content to stdout or a file.
160
+ *
161
+ * If no output path is provided, writes to stdout (for piping/redirection).
162
+ * If an output path is provided, creates parent directories if needed and writes the file,
163
+ * overwriting any existing file at that path.
164
+ *
165
+ * @param reportContent - The markdown report content to write.
166
+ * @param outputPath - Optional path to output file. If not provided, writes to stdout.
167
+ * @throws {Error} If file writing fails.
168
+ */
169
+ async function writeReport(reportContent: string, outputPath?: string): Promise<void> {
170
+ if (!outputPath) {
171
+ // Write to stdout (allows piping: report --debate file.json > output.md)
172
+ process.stdout.write(reportContent);
173
+ return;
174
+ }
175
+
176
+ // Write to file (handles path normalization and directory creation)
177
+ const writtenPath = await writeFileWithDirectories(outputPath, reportContent);
178
+ infoUser(`Generated report: ${writtenPath}`);
179
+ }
180
+
181
+ /**
182
+ * Registers the 'report' CLI command, which generates a markdown report from a saved debate state.
183
+ *
184
+ * This command loads a debate state JSON file, loads the corresponding configuration file,
185
+ * matches agent configurations with agent IDs found in the debate, and generates a markdown report.
186
+ *
187
+ * @param program - Commander.js program object to which the command is added.
188
+ *
189
+ * Command-line Options:
190
+ * --debate <path> Required: Path to debate JSON file (DebateState format).
191
+ * --config <path> Optional: Path to configuration file. If not provided, creates minimal configs from debate state.
192
+ * --output <path> Optional: Path to output markdown file. If not provided, writes to stdout.
193
+ * -v, --verbose Optional: Enable verbose mode for report generation.
194
+ *
195
+ * Behavior:
196
+ * - Loads and validates the debate state file.
197
+ * - If --config is provided: loads configuration file and matches agent/judge configs with IDs found in debate state.
198
+ * - If --config is not provided: creates minimal agent/judge configs from debate state (no validation of IDs).
199
+ * - Generates markdown report using generateDebateReport.
200
+ * - Writes report to file or stdout.
201
+ *
202
+ * Errors:
203
+ * - Exits with explicit error codes and user-friendly messages on invalid arguments,
204
+ * missing files, or report generation failures.
205
+ */
206
+ export function reportCommand(program: Command) {
207
+ program
208
+ .command('report')
209
+ .requiredOption('--debate <path>', 'Path to debate JSON file (DebateState)')
210
+ .option('--config <path>', 'Path to configuration file (default: ./debate-config.json)')
211
+ .option('-o, --output <path>', 'Path to output markdown file (default: stdout)')
212
+ .option('-v, --verbose', 'Verbose mode for report generation')
213
+ .description('Generate a markdown report from a saved debate state')
214
+ .action(async (options: any) => {
215
+ try {
216
+ // Load and validate debate state
217
+ const debateState = loadAndValidateDebateState(options.debate);
218
+
219
+ let agentConfigs: AgentConfig[];
220
+ let judgeConfig: AgentConfig;
221
+
222
+ // Only load config if --config is explicitly provided
223
+ if (options.config) {
224
+ // Load configuration to get agent and judge configs
225
+ const sysConfig = await loadConfig(options.config);
226
+
227
+ // Extract agent IDs from debate state
228
+ const agentInfo = extractAgentInfoFromDebate(debateState);
229
+ const agentIds = Array.from(agentInfo.keys());
230
+
231
+ // Match agent configs with agent IDs from debate
232
+ agentConfigs = matchAgentConfigsFromSysConfig(sysConfig, agentIds);
233
+
234
+ // Get judge config (loadConfig ensures judge is always present, either from config or defaults)
235
+ // This check is defensive but should never fail in practice
236
+ if (!sysConfig.judge) {
237
+ throw createValidationError('Configuration missing judge definition', EXIT_INVALID_ARGS);
238
+ }
239
+ judgeConfig = sysConfig.judge;
240
+ } else {
241
+ // No config provided - create minimal configs from debate state
242
+ agentConfigs = createMinimalAgentConfigsFromDebate(debateState);
243
+ judgeConfig = createMinimalJudgeConfigFromDebate(debateState);
244
+ }
245
+
246
+ // Generate report
247
+ const reportContent = generateDebateReport(
248
+ debateState,
249
+ agentConfigs,
250
+ judgeConfig,
251
+ debateState.problem,
252
+ { verbose: options.verbose || false }
253
+ );
254
+
255
+ // Write report
256
+ await writeReport(reportContent, options.output);
257
+ } catch (err: any) {
258
+ const code = typeof err?.code === 'number' ? err.code : EXIT_GENERAL_ERROR;
259
+ writeStderr((err?.message || 'Unknown error') + '\n');
260
+ // Rethrow for runCli catch to set process exit when direct run
261
+ throw Object.assign(new Error(err?.message || 'Unknown error'), { code });
262
+ }
263
+ });
264
+ }
265
+
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { debateCommand, loadConfig as loadDebateConfig } from './commands/debate';
4
+ import { evalCommand } from './commands/eval';
5
+ import { reportCommand } from './commands/report';
6
+ import { EXIT_GENERAL_ERROR } from '../utils/exit-codes';
7
+
8
+ // Color constants for CLI output
9
+ export const WARNING_COLOR = 'yellow';
10
+ export const INFO_COLOR = 'gray';
11
+ export const PROGRAM_NAME = 'dialectic';
12
+
13
+ // Lazy optional chalk import for colored output
14
+ let chalk: any;
15
+ try { chalk = require('chalk'); } catch { chalk = null; }
16
+
17
+ function color(method: string, msg: string): string {
18
+ return chalk && chalk[method] ? chalk[method](msg) : msg;
19
+ }
20
+
21
+ /**
22
+ * Outputs a warning message to stderr with consistent formatting.
23
+ * Used for user-facing warnings throughout the CLI.
24
+ */
25
+ export function warnUser(message: string): void {
26
+ process.stderr.write(color(WARNING_COLOR, message) + '\n');
27
+ }
28
+
29
+ /**
30
+ * Outputs an info message to stderr with consistent formatting.
31
+ * Used for user-facing informational messages throughout the CLI.
32
+ */
33
+ export function infoUser(message: string): void {
34
+ process.stderr.write(color(INFO_COLOR, message) + '\n');
35
+ }
36
+
37
+ /**
38
+ * Outputs a diagnostic/verbose message to stderr without coloring.
39
+ * Used for structured diagnostic output that should not interfere with stdout piping.
40
+ */
41
+ export function writeStderr(message: string): void {
42
+ process.stderr.write(message);
43
+ }
44
+
45
+ /**
46
+ * Runs the CLI for the multi-agent debate system.
47
+ *
48
+ * This function sets up the command-line interface using Commander,
49
+ * registers available commands (such as 'debate'), and parses the provided arguments.
50
+ * It is intended to be called with the argument vector (argv) excluding the node and script name.
51
+ *
52
+ * @param argv - The array of command-line arguments to parse (excluding 'node' and script name).
53
+ * @throws Any error encountered during command parsing.
54
+ */
55
+ export async function runCli(argv: string[]) {
56
+ const program = new Command();
57
+ program.name(PROGRAM_NAME).description('Multi-agent debate system').version('0.1.0');
58
+
59
+ // Register commands
60
+ debateCommand(program);
61
+ evalCommand(program);
62
+ reportCommand(program);
63
+
64
+ await program.parseAsync(['node', PROGRAM_NAME, ...argv]);
65
+ }
66
+
67
+ // If called directly from node
68
+ if (require.main === module) {
69
+ runCli(process.argv.slice(2)).catch((err: any) => {
70
+ // Map generic error when not already code-tagged
71
+ const code = typeof err?.code === 'number' ? err.code : EXIT_GENERAL_ERROR;
72
+ const msg = err?.message || 'Unknown error';
73
+ process.stderr.write(msg + '\n');
74
+ process.exit(code);
75
+ });
76
+ }
77
+
78
+ // Re-export config loader for tests
79
+ export const loadConfig = loadDebateConfig;
@@ -0,0 +1,198 @@
1
+ import { AgentConfig, Proposal, Critique, ContributionMetadata } from '../types/agent.types';
2
+ import { DebateContext, ContextPreparationResult, ClarificationQuestionsResponse } from '../types/debate.types';
3
+ import { LLMProvider } from '../providers/llm-provider';
4
+ import { CompletionResponse, CompletionUsage } from '../providers/llm-provider';
5
+
6
+
7
+
8
+ /**
9
+ * Abstract base class representing an AI agent in the multi-agent debate system.
10
+ *
11
+ * Agents are responsible for generating proposals, critiquing other agents' proposals,
12
+ * and refining their own proposals based on received critiques. Each agent is configured
13
+ * with a specific role, LLM model, and provider, and interacts with an LLMProvider to
14
+ * generate its outputs.
15
+ *
16
+ * Additionally, agents manage context summarization to handle large debate histories.
17
+ *
18
+ * Subclasses must implement the core debate methods:
19
+ * - propose: Generate a solution proposal for a given problem.
20
+ * - critique: Critique another agent's proposal.
21
+ * - refine: Refine an original proposal by incorporating critiques.
22
+ * - shouldSummarize: Determine if context summarization is needed.
23
+ * - prepareContext: Prepare and potentially summarize the debate context.
24
+ *
25
+ * The base class provides a utility method, callLLM, to standardize LLM interactions,
26
+ * capturing latency and usage metadata.
27
+ *
28
+ * @template Proposal - The type representing a proposal response.
29
+ * @template Critique - The type representing a critique response.
30
+ */
31
+ export abstract class Agent {
32
+ /**
33
+ * Constructs an Agent.
34
+ * @param config - The agent's configuration, including model, role, and prompts.
35
+ * @param provider - The LLMProvider instance used for LLM interactions.
36
+ */
37
+ constructor(public config: AgentConfig, protected provider: LLMProvider) {}
38
+
39
+ /**
40
+ * Generates a proposal for the given problem.
41
+ * @param problem - The software design problem to solve.
42
+ * @param context - The current debate context, including history and state.
43
+ * @returns A Promise resolving to a Proposal object containing the agent's solution and metadata.
44
+ */
45
+ abstract propose(problem: string, context: DebateContext): Promise<Proposal>;
46
+
47
+ /**
48
+ * Critiques a given proposal from another agent.
49
+ * @param proposal - The proposal to critique.
50
+ * @param context - The current debate context.
51
+ * @returns A Promise resolving to a Critique object containing the agent's review and metadata.
52
+ */
53
+ abstract critique(proposal: Proposal, context: DebateContext): Promise<Critique>;
54
+
55
+ /**
56
+ * Refines the agent's original proposal by addressing critiques and incorporating suggestions.
57
+ * @param originalProposal - The original proposal to refine.
58
+ * @param critiques - Array of critiques to address.
59
+ * @param context - The current debate context.
60
+ * @returns A Promise resolving to a new Proposal object with the refined solution and metadata.
61
+ */
62
+ abstract refine(originalProposal: Proposal, critiques: Critique[], context: DebateContext): Promise<Proposal>;
63
+
64
+ /**
65
+ * Determines whether the debate context should be summarized based on configured thresholds.
66
+ *
67
+ * @param context - The current debate context to evaluate.
68
+ * @returns True if summarization should occur, false otherwise.
69
+ */
70
+ abstract shouldSummarize(context: DebateContext): boolean;
71
+
72
+ /**
73
+ * Prepares the debate context for the agent, potentially summarizing it if needed.
74
+ *
75
+ * This method evaluates whether summarization is necessary and, if so, generates
76
+ * a concise summary of the debate history from the agent's perspective.
77
+ *
78
+ * @param context - The current debate context.
79
+ * @param roundNumber - The current round number (1-indexed).
80
+ * @returns A promise resolving to the context preparation result.
81
+ */
82
+ abstract prepareContext( context: DebateContext, roundNumber: number ): Promise<ContextPreparationResult>;
83
+
84
+ /**
85
+ * Requests role-specific clarifying questions before the debate starts.
86
+ * Implementations should return ONLY structured questions; zero or more items are allowed.
87
+ *
88
+ * @param problem - The software design problem to clarify.
89
+ * @param context - The current debate context.
90
+ */
91
+ abstract askClarifyingQuestions(problem: string, context: DebateContext): Promise<ClarificationQuestionsResponse>;
92
+
93
+ /**
94
+ * Template method for generating proposals.
95
+ * Subclasses should call this method from their `propose` implementation after preparing prompts.
96
+ *
97
+ * @final
98
+ * @param _context - The current debate context (unused in base implementation).
99
+ * @param systemPrompt - The system prompt to use for the LLM.
100
+ * @param userPrompt - The user prompt to use for the LLM.
101
+ * @returns A Promise resolving to a Proposal object containing the agent's solution and metadata.
102
+ */
103
+ protected async proposeImpl(
104
+
105
+ _context: DebateContext,
106
+ systemPrompt: string,
107
+ userPrompt: string
108
+ ): Promise<Proposal> {
109
+ const { text, usage, latencyMs } = await this.callLLM(systemPrompt, userPrompt);
110
+ const metadata: ContributionMetadata = { latencyMs, model: this.config.model };
111
+ if (usage?.totalTokens != null) metadata.tokensUsed = usage.totalTokens;
112
+ return { content: text, metadata };
113
+ }
114
+
115
+ /**
116
+ * Template method for generating critiques.
117
+ * Subclasses should call this method from their `critique` implementation after preparing prompts.
118
+ *
119
+ * @final
120
+ * @param _proposal - The proposal to critique.
121
+ * @param _context - The current debate context.
122
+ * @param systemPrompt - The system prompt to use for the LLM.
123
+ * @param userPrompt - The user prompt to use for the LLM.
124
+ * @returns A Promise resolving to a Critique object containing the agent's review and metadata.
125
+ */
126
+ protected async critiqueImpl(
127
+ _proposal: Proposal,
128
+ _context: DebateContext,
129
+ systemPrompt: string,
130
+ userPrompt: string
131
+ ): Promise<Critique> {
132
+ const { text, usage, latencyMs } = await this.callLLM(systemPrompt, userPrompt);
133
+ const metadata: ContributionMetadata = { latencyMs, model: this.config.model };
134
+ if (usage?.totalTokens != null) metadata.tokensUsed = usage.totalTokens;
135
+ return { content: text, metadata };
136
+ }
137
+
138
+ /**
139
+ * Template method for refining proposals.
140
+ * Subclasses should call this method from their `refine` implementation after preparing prompts.
141
+ *
142
+ * @final
143
+ * @param _originalProposal - The original proposal to refine.
144
+ * @param _critiques - Array of critiques to address.
145
+ * @param _context - The current debate context.
146
+ * @param systemPrompt - The system prompt to use for the LLM.
147
+ * @param userPrompt - The user prompt to use for the LLM.
148
+ * @returns A Promise resolving to a refined Proposal object with updated content and metadata.
149
+ */
150
+ protected async refineImpl(
151
+ _originalProposal: Proposal,
152
+ _critiques: Critique[],
153
+ _context: DebateContext,
154
+ systemPrompt: string,
155
+ userPrompt: string
156
+ ): Promise<Proposal> {
157
+ const { text, usage, latencyMs } = await this.callLLM(systemPrompt, userPrompt);
158
+ const metadata: ContributionMetadata = { latencyMs, model: this.config.model };
159
+ if (usage?.totalTokens != null) metadata.tokensUsed = usage.totalTokens;
160
+ return { content: text, metadata };
161
+ }
162
+
163
+ /**
164
+ * Helper method to call the underlying LLM provider with the specified prompts.
165
+ * Measures latency and returns the generated text, usage statistics, and latency.
166
+ *
167
+ * @param systemPrompt - The system prompt to prime the LLM.
168
+ * @param userPrompt - The user prompt representing the agent's request.
169
+ * @returns A Promise resolving to an AgentLLMResponse containing text, usage metadata, and latency.
170
+ */
171
+ protected async callLLM(systemPrompt: string, userPrompt: string): Promise<AgentLLMResponse> {
172
+ const started = Date.now();
173
+ const res : CompletionResponse = await this.provider.complete({
174
+ model: this.config.model,
175
+ temperature: this.config.temperature,
176
+ systemPrompt,
177
+ userPrompt,
178
+ });
179
+ const latencyMs = Date.now() - started;
180
+ const response: AgentLLMResponse = { text: res.text, latencyMs };
181
+ if (res.usage) response.usage = res.usage;
182
+ return response;
183
+ }
184
+ }
185
+
186
+
187
+ /**
188
+ * Represents the response from an LLM call made by an agent.
189
+ *
190
+ * @property text - The main textual output generated by the LLM.
191
+ * @property usage - (Optional) Token usage statistics for the LLM call.
192
+ * @property latencyMs - The time taken (in milliseconds) to complete the LLM call.
193
+ */
194
+ export interface AgentLLMResponse {
195
+ text: string;
196
+ usage?: CompletionUsage;
197
+ latencyMs: number;
198
+ }
@@ -0,0 +1,34 @@
1
+ import { Agent } from './agent';
2
+ import { AgentClarifications } from '../types/debate.types';
3
+
4
+ /**
5
+ * Collects clarifying questions from each agent, enforcing a per-agent limit and returning
6
+ * normalized groups with empty answers to be filled by the CLI.
7
+ *
8
+ * @param problem - The problem statement to clarify
9
+ * @param agents - List of agents participating
10
+ * @param maxPerAgent - Maximum number of questions allowed per agent
11
+ * @param warn - Warning output function for user-facing messages
12
+ */
13
+ export async function collectClarifications( problem: string, agents: Agent[], maxPerAgent: number, warn: (message: string) => void ): Promise<AgentClarifications[]>
14
+ {
15
+ const agentPromises = agents.map(async (a) => {
16
+ const ctx = { problem } as any;
17
+ const res = await a.askClarifyingQuestions(problem, ctx);
18
+ const list = Array.isArray(res?.questions) ? res.questions : [];
19
+ const truncated = list.slice(0, maxPerAgent);
20
+ if (list.length > maxPerAgent) {
21
+ warn(`Agent ${a.config.name} returned ${list.length} questions; limited to ${maxPerAgent}.`);
22
+ }
23
+ return {
24
+ agentId: a.config.id,
25
+ agentName: a.config.name,
26
+ role: a.config.role,
27
+ items: truncated.map((q) => ({ id: q.id!, question: q.text, answer: '' }))
28
+ };
29
+ });
30
+
31
+ return Promise.all(agentPromises);
32
+ }
33
+
34
+