@cogitator-ai/swarms 0.4.19 → 0.5.1

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 (154) hide show
  1. package/README.md +245 -897
  2. package/dist/agents.d.ts +13 -0
  3. package/dist/agents.d.ts.map +1 -0
  4. package/dist/agents.js +70 -0
  5. package/dist/agents.js.map +1 -0
  6. package/dist/assessor/assessor.d.ts +9 -2
  7. package/dist/assessor/assessor.d.ts.map +1 -1
  8. package/dist/assessor/assessor.js +89 -61
  9. package/dist/assessor/assessor.js.map +1 -1
  10. package/dist/assessor/role-matcher.d.ts +6 -24
  11. package/dist/assessor/role-matcher.d.ts.map +1 -1
  12. package/dist/assessor/role-matcher.js +9 -37
  13. package/dist/assessor/role-matcher.js.map +1 -1
  14. package/dist/assessor/scoring.js +1 -1
  15. package/dist/assessor/scoring.js.map +1 -1
  16. package/dist/assessor/task-analyzer.d.ts.map +1 -1
  17. package/dist/assessor/task-analyzer.js +4 -19
  18. package/dist/assessor/task-analyzer.js.map +1 -1
  19. package/dist/base-coordinator.d.ts +117 -0
  20. package/dist/base-coordinator.d.ts.map +1 -0
  21. package/dist/base-coordinator.js +455 -0
  22. package/dist/base-coordinator.js.map +1 -0
  23. package/dist/communication/blackboard.d.ts +34 -4
  24. package/dist/communication/blackboard.d.ts.map +1 -1
  25. package/dist/communication/blackboard.js +80 -41
  26. package/dist/communication/blackboard.js.map +1 -1
  27. package/dist/communication/event-emitter.d.ts +1 -1
  28. package/dist/communication/event-emitter.d.ts.map +1 -1
  29. package/dist/communication/event-emitter.js +6 -17
  30. package/dist/communication/event-emitter.js.map +1 -1
  31. package/dist/communication/index.d.ts +2 -2
  32. package/dist/communication/index.d.ts.map +1 -1
  33. package/dist/communication/index.js +2 -2
  34. package/dist/communication/index.js.map +1 -1
  35. package/dist/communication/message-bus.d.ts +48 -5
  36. package/dist/communication/message-bus.d.ts.map +1 -1
  37. package/dist/communication/message-bus.js +135 -50
  38. package/dist/communication/message-bus.js.map +1 -1
  39. package/dist/communication/redis-blackboard.d.ts +12 -5
  40. package/dist/communication/redis-blackboard.d.ts.map +1 -1
  41. package/dist/communication/redis-blackboard.js +214 -94
  42. package/dist/communication/redis-blackboard.js.map +1 -1
  43. package/dist/communication/redis-event-emitter.d.ts +14 -1
  44. package/dist/communication/redis-event-emitter.d.ts.map +1 -1
  45. package/dist/communication/redis-event-emitter.js +107 -47
  46. package/dist/communication/redis-event-emitter.js.map +1 -1
  47. package/dist/communication/redis-message-bus.d.ts +13 -6
  48. package/dist/communication/redis-message-bus.d.ts.map +1 -1
  49. package/dist/communication/redis-message-bus.js +93 -66
  50. package/dist/communication/redis-message-bus.js.map +1 -1
  51. package/dist/coordinator.d.ts +7 -41
  52. package/dist/coordinator.d.ts.map +1 -1
  53. package/dist/coordinator.js +17 -336
  54. package/dist/coordinator.js.map +1 -1
  55. package/dist/distributed/distributed-coordinator.d.ts +55 -49
  56. package/dist/distributed/distributed-coordinator.d.ts.map +1 -1
  57. package/dist/distributed/distributed-coordinator.js +166 -253
  58. package/dist/distributed/distributed-coordinator.js.map +1 -1
  59. package/dist/distributed/index.d.ts +1 -1
  60. package/dist/distributed/index.d.ts.map +1 -1
  61. package/dist/distributed/index.js +1 -1
  62. package/dist/distributed/index.js.map +1 -1
  63. package/dist/index.d.ts +4 -3
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +4 -3
  66. package/dist/index.js.map +1 -1
  67. package/dist/resources/circuit-breaker.d.ts +1 -6
  68. package/dist/resources/circuit-breaker.d.ts.map +1 -1
  69. package/dist/resources/circuit-breaker.js +14 -17
  70. package/dist/resources/circuit-breaker.js.map +1 -1
  71. package/dist/resources/index.d.ts.map +1 -1
  72. package/dist/resources/index.js.map +1 -1
  73. package/dist/resources/tracker.d.ts.map +1 -1
  74. package/dist/resources/tracker.js +8 -5
  75. package/dist/resources/tracker.js.map +1 -1
  76. package/dist/shared/hierarchy.d.ts +21 -0
  77. package/dist/shared/hierarchy.d.ts.map +1 -0
  78. package/dist/shared/hierarchy.js +27 -0
  79. package/dist/shared/hierarchy.js.map +1 -0
  80. package/dist/shared/negotiation.d.ts +27 -0
  81. package/dist/shared/negotiation.d.ts.map +1 -0
  82. package/dist/shared/negotiation.js +55 -0
  83. package/dist/shared/negotiation.js.map +1 -0
  84. package/dist/shared/voting.d.ts +6 -0
  85. package/dist/shared/voting.d.ts.map +1 -0
  86. package/dist/shared/voting.js +13 -0
  87. package/dist/shared/voting.js.map +1 -0
  88. package/dist/strategies/auction.js +2 -3
  89. package/dist/strategies/auction.js.map +1 -1
  90. package/dist/strategies/consensus.d.ts +12 -0
  91. package/dist/strategies/consensus.d.ts.map +1 -1
  92. package/dist/strategies/consensus.js +81 -40
  93. package/dist/strategies/consensus.js.map +1 -1
  94. package/dist/strategies/debate.d.ts.map +1 -1
  95. package/dist/strategies/debate.js +19 -9
  96. package/dist/strategies/debate.js.map +1 -1
  97. package/dist/strategies/hierarchical.d.ts.map +1 -1
  98. package/dist/strategies/hierarchical.js +29 -12
  99. package/dist/strategies/hierarchical.js.map +1 -1
  100. package/dist/strategies/index.d.ts +8 -7
  101. package/dist/strategies/index.d.ts.map +1 -1
  102. package/dist/strategies/index.js +1 -7
  103. package/dist/strategies/index.js.map +1 -1
  104. package/dist/strategies/negotiation/approval.d.ts.map +1 -1
  105. package/dist/strategies/negotiation/approval.js +47 -21
  106. package/dist/strategies/negotiation/approval.js.map +1 -1
  107. package/dist/strategies/negotiation/convergence.js +1 -1
  108. package/dist/strategies/negotiation-strategy.d.ts +24 -2
  109. package/dist/strategies/negotiation-strategy.d.ts.map +1 -1
  110. package/dist/strategies/negotiation-strategy.js +281 -158
  111. package/dist/strategies/negotiation-strategy.js.map +1 -1
  112. package/dist/strategies/pipeline.d.ts.map +1 -1
  113. package/dist/strategies/pipeline.js +19 -11
  114. package/dist/strategies/pipeline.js.map +1 -1
  115. package/dist/swarm.d.ts +24 -15
  116. package/dist/swarm.d.ts.map +1 -1
  117. package/dist/swarm.js +193 -116
  118. package/dist/swarm.js.map +1 -1
  119. package/dist/tools/blackboard.d.ts +1 -1
  120. package/dist/tools/blackboard.d.ts.map +1 -1
  121. package/dist/tools/blackboard.js +26 -6
  122. package/dist/tools/blackboard.js.map +1 -1
  123. package/dist/tools/delegation.d.ts +23 -4
  124. package/dist/tools/delegation.d.ts.map +1 -1
  125. package/dist/tools/delegation.js +116 -85
  126. package/dist/tools/delegation.js.map +1 -1
  127. package/dist/tools/index.d.ts +3 -1
  128. package/dist/tools/index.d.ts.map +1 -1
  129. package/dist/tools/index.js +19 -4
  130. package/dist/tools/index.js.map +1 -1
  131. package/dist/tools/messaging.d.ts +36 -7
  132. package/dist/tools/messaging.d.ts.map +1 -1
  133. package/dist/tools/messaging.js +122 -46
  134. package/dist/tools/messaging.js.map +1 -1
  135. package/dist/tools/negotiation.d.ts +8 -4
  136. package/dist/tools/negotiation.d.ts.map +1 -1
  137. package/dist/tools/negotiation.js +135 -85
  138. package/dist/tools/negotiation.js.map +1 -1
  139. package/dist/tools/voting.d.ts +3 -0
  140. package/dist/tools/voting.d.ts.map +1 -1
  141. package/dist/tools/voting.js +13 -12
  142. package/dist/tools/voting.js.map +1 -1
  143. package/dist/utils/concurrency.d.ts +10 -0
  144. package/dist/utils/concurrency.d.ts.map +1 -0
  145. package/dist/utils/concurrency.js +28 -0
  146. package/dist/utils/concurrency.js.map +1 -0
  147. package/dist/utils/invoke.d.ts +2 -0
  148. package/dist/utils/invoke.d.ts.map +1 -0
  149. package/dist/utils/invoke.js +19 -0
  150. package/dist/utils/invoke.js.map +1 -0
  151. package/dist/workflow/swarm-node.d.ts.map +1 -1
  152. package/dist/workflow/swarm-node.js +56 -71
  153. package/dist/workflow/swarm-node.js.map +1 -1
  154. package/package.json +6 -6
@@ -4,12 +4,17 @@ import { BaseStrategy } from './base.js';
4
4
  import { TurnManager } from './negotiation/turn-manager.js';
5
5
  import { ConvergenceCalculator } from './negotiation/convergence.js';
6
6
  import { ApprovalIntegration } from './negotiation/approval.js';
7
+ import { MEDIATOR_ID, NEGOTIATION_RULES_SECTION, NEGOTIATION_SECTION, isFullyAccepted, isOfferExpired, offerParties, readNegotiationState, } from '../shared/negotiation.js';
7
8
  export class NegotiationStrategy extends BaseStrategy {
8
9
  config;
9
10
  state;
11
+ agentNames = [];
10
12
  turnManager;
11
13
  convergenceCalculator;
12
14
  approvalIntegration;
15
+ reviewedOfferIds = new Set();
16
+ reviewedCoalitionIds = new Set();
17
+ lastMediationRound;
13
18
  constructor(coordinator, config = {}) {
14
19
  super(coordinator);
15
20
  this.config = { ...DEFAULT_NEGOTIATION_CONFIG, ...config };
@@ -22,45 +27,68 @@ export class NegotiationStrategy extends BaseStrategy {
22
27
  if (negotiatingAgents.length < 2) {
23
28
  throw new Error('Negotiation strategy requires at least 2 agents');
24
29
  }
25
- const agentNames = negotiatingAgents.map((a) => a.agent.name);
26
- this.initializeState(negotiationId, agentNames);
27
- this.initializeHelpers(agentNames, negotiationId);
30
+ this.agentNames = negotiatingAgents.map((a) => a.agent.name);
31
+ this.reviewedOfferIds.clear();
32
+ this.reviewedCoalitionIds.clear();
33
+ this.lastMediationRound = undefined;
34
+ this.initializeState(negotiationId);
35
+ this.initializeHelpers(this.agentNames, negotiationId);
28
36
  this.coordinator.events.emit('negotiation:start', {
29
37
  negotiationId,
30
- agents: agentNames,
38
+ agents: this.agentNames,
31
39
  config: this.config,
32
40
  }, 'system');
33
- await this.runInitializationPhase(options, negotiatingAgents, agentResults);
34
- for (let round = 1; round <= this.config.maxRounds; round++) {
35
- this.state.round = round;
36
- this.turnManager.setRound(round);
37
- this.coordinator.events.emit('negotiation:round', { round, maxRounds: this.config.maxRounds }, 'system');
38
- await this.runProposalPhase(options, negotiatingAgents, agentResults);
39
- if (await this.checkForAgreement()) {
40
- return this.finalizeResult(agentResults, 'agreement');
41
- }
42
- await this.runCounterPhase(options, negotiatingAgents, agentResults);
43
- if (await this.checkForAgreement()) {
44
- return this.finalizeResult(agentResults, 'agreement');
45
- }
46
- const metrics = this.convergenceCalculator.calculateOverallConvergence(this.state.offers, round);
47
- this.state.convergenceHistory.push(metrics);
48
- this.coordinator.events.emit('negotiation:convergence-update', { metrics, round }, 'system');
49
- if (this.convergenceCalculator.isStagnant()) {
50
- this.coordinator.events.emit('negotiation:stagnation-detected', { round, metrics }, 'system');
51
- const mediationSuggestion = this.convergenceCalculator.suggestCompromise(this.state.offers);
52
- if (mediationSuggestion) {
53
- this.coordinator.events.emit('negotiation:mediation-suggested', { suggestion: mediationSuggestion }, 'system');
54
- await this.runRefinementPhase(options, negotiatingAgents, agentResults, mediationSuggestion);
41
+ try {
42
+ await this.runInitializationPhase(options, negotiatingAgents, agentResults);
43
+ for (let round = 1; round <= this.config.maxRounds; round++) {
44
+ this.state.round = round;
45
+ this.turnManager.setRound(round);
46
+ this.updateBlackboard();
47
+ this.coordinator.events.emit('negotiation:round', { round, maxRounds: this.config.maxRounds }, 'system');
48
+ await this.runProposalPhase(options, negotiatingAgents, agentResults);
49
+ if (await this.checkForAgreement()) {
50
+ return this.finalizeResult(agentResults, 'agreement');
51
+ }
52
+ await this.runCounterPhase(options, negotiatingAgents, agentResults);
53
+ if (await this.checkForAgreement()) {
54
+ return this.finalizeResult(agentResults, 'agreement');
55
+ }
56
+ const metrics = this.convergenceCalculator.calculateOverallConvergence(this.state.offers, round);
57
+ this.state.convergenceHistory = [...this.state.convergenceHistory, metrics];
58
+ this.updateBlackboard();
59
+ this.coordinator.events.emit('negotiation:convergence-update', { metrics, round }, 'system');
60
+ if (this.convergenceCalculator.isStagnant() && this.lastMediationRound === undefined) {
61
+ this.coordinator.events.emit('negotiation:stagnation-detected', { round, metrics }, 'system');
62
+ const mediationSuggestion = this.convergenceCalculator.suggestCompromise(this.state.offers.filter((o) => o.from !== MEDIATOR_ID));
63
+ if (mediationSuggestion) {
64
+ this.lastMediationRound = round;
65
+ this.coordinator.events.emit('negotiation:mediation-suggested', { suggestion: mediationSuggestion }, 'system');
66
+ await this.runRefinementPhase(options, negotiatingAgents, agentResults, mediationSuggestion);
67
+ if (await this.checkForAgreement()) {
68
+ return this.finalizeResult(agentResults, 'agreement');
69
+ }
70
+ }
71
+ }
72
+ if (this.isDeadlocked(round)) {
73
+ return this.handleDeadlock(options, agentResults);
55
74
  }
56
75
  }
57
- if (this.isDeadlocked()) {
58
- return this.handleDeadlock(options, agents, agentResults);
59
- }
76
+ return this.handleDeadlock(options, agentResults);
77
+ }
78
+ finally {
79
+ this.approvalIntegration?.cancelAll();
80
+ }
81
+ }
82
+ /**
83
+ * Answer a pending approval request (see the `negotiation:approval-required` event).
84
+ */
85
+ respondToApproval(requestId, response) {
86
+ if (!this.approvalIntegration) {
87
+ throw new Error('No approval gates are configured for this negotiation');
60
88
  }
61
- return this.handleDeadlock(options, agents, agentResults);
89
+ this.approvalIntegration.submitResponse(requestId, response);
62
90
  }
63
- initializeState(negotiationId, _agentNames) {
91
+ initializeState(negotiationId) {
64
92
  this.state = {
65
93
  negotiationId,
66
94
  phase: 'initialization',
@@ -76,7 +104,14 @@ export class NegotiationStrategy extends BaseStrategy {
76
104
  startedAt: Date.now(),
77
105
  lastActivityAt: Date.now(),
78
106
  };
79
- this.coordinator.blackboard.write('negotiation', this.state, 'system');
107
+ const rules = {
108
+ maxOffersPerRound: this.config.maxOffersPerRound,
109
+ offerTimeout: this.config.offerTimeout,
110
+ allowCoalitions: this.config.allowCoalitions ?? true,
111
+ minCoalitionSize: this.config.minCoalitionSize ?? 2,
112
+ };
113
+ this.coordinator.blackboard.write(NEGOTIATION_RULES_SECTION, rules, 'system');
114
+ this.updateBlackboard();
80
115
  }
81
116
  initializeHelpers(agentNames, negotiationId) {
82
117
  this.turnManager = new TurnManager({
@@ -89,12 +124,45 @@ export class NegotiationStrategy extends BaseStrategy {
89
124
  stagnationThreshold: this.config.stagnationThreshold ?? 0.05,
90
125
  maxRoundsWithoutProgress: this.config.maxRoundsWithoutProgress ?? 3,
91
126
  });
92
- if (this.config.approvalGates && this.config.approvalGates.length > 0) {
93
- this.approvalIntegration = new ApprovalIntegration({
94
- gates: this.config.approvalGates,
95
- negotiationId,
96
- });
127
+ this.approvalIntegration =
128
+ this.config.approvalGates && this.config.approvalGates.length > 0
129
+ ? new ApprovalIntegration({ gates: this.config.approvalGates, negotiationId })
130
+ : undefined;
131
+ }
132
+ /**
133
+ * Run one agent turn. A turn that exceeds `turnTimeout` is skipped instead of failing
134
+ * the negotiation; any other error propagates.
135
+ */
136
+ async runTurn(agent, prompt, options, phase, results, resultKey) {
137
+ const name = agent.agent.name;
138
+ const turnTimeout = this.config.turnTimeout;
139
+ const startedAt = Date.now();
140
+ this.state.currentTurn = name;
141
+ this.turnManager.startTurn();
142
+ this.updateBlackboard();
143
+ try {
144
+ const result = await this.coordinator.runAgent(name, prompt, this.buildAgentContext(agent, options), turnTimeout !== undefined && turnTimeout > 0 ? { timeout: turnTimeout } : undefined);
145
+ results.set(resultKey, result);
146
+ this.syncState();
147
+ this.state.turnHistory = [
148
+ ...this.state.turnHistory,
149
+ { agent: name, round: this.state.round, action: `${phase}_turn`, timestamp: Date.now() },
150
+ ];
151
+ }
152
+ catch (error) {
153
+ const timedOut = turnTimeout !== undefined && turnTimeout > 0 && Date.now() - startedAt >= turnTimeout;
154
+ if (!timedOut)
155
+ throw error;
156
+ this.syncState();
157
+ this.state.turnHistory = [
158
+ ...this.state.turnHistory,
159
+ { agent: name, round: this.state.round, action: 'turn_skipped', timestamp: Date.now() },
160
+ ];
161
+ this.coordinator.events.emit('negotiation:turn', { agent: name, round: this.state.round, phase, skipped: true, reason: 'timeout' }, name);
97
162
  }
163
+ this.state.lastActivityAt = Date.now();
164
+ this.updateBlackboard();
165
+ await this.reviewNewActivity();
98
166
  }
99
167
  async runInitializationPhase(options, agents, results) {
100
168
  this.state.phase = 'initialization';
@@ -102,15 +170,13 @@ export class NegotiationStrategy extends BaseStrategy {
102
170
  this.coordinator.events.emit('negotiation:phase-change', { phase: 'initialization' }, 'system');
103
171
  const initPrompt = this.buildInitializationPrompt(options.input);
104
172
  for (const agent of agents) {
105
- const result = await this.coordinator.runAgent(agent.agent.name, initPrompt, this.buildAgentContext(agent, options));
106
- results.set(`${agent.agent.name}_init`, result);
107
- this.state.turnHistory.push({
108
- agent: agent.agent.name,
109
- round: 0,
110
- action: 'declared_interests',
111
- timestamp: Date.now(),
112
- });
173
+ await this.runTurn(agent, initPrompt, options, 'init', results, `${agent.agent.name}_init`);
174
+ this.state.turnHistory = [
175
+ ...this.state.turnHistory,
176
+ { agent: agent.agent.name, round: 0, action: 'declared_interests', timestamp: Date.now() },
177
+ ];
113
178
  }
179
+ this.state.currentTurn = null;
114
180
  this.state.lastActivityAt = Date.now();
115
181
  this.updateBlackboard();
116
182
  }
@@ -120,12 +186,8 @@ export class NegotiationStrategy extends BaseStrategy {
120
186
  this.coordinator.events.emit('negotiation:phase-change', { phase: 'proposal', round: this.state.round }, 'system');
121
187
  const proposalPrompt = this.buildProposalPrompt(options.input);
122
188
  for (const agent of agents) {
123
- this.state.currentTurn = agent.agent.name;
124
- this.turnManager.startTurn();
125
- this.updateBlackboard();
126
189
  this.coordinator.events.emit('negotiation:turn', { agent: agent.agent.name, round: this.state.round, phase: 'proposal' }, agent.agent.name);
127
- const result = await this.coordinator.runAgent(agent.agent.name, proposalPrompt, this.buildAgentContext(agent, options));
128
- results.set(`${agent.agent.name}_proposal_r${this.state.round}`, result);
190
+ await this.runTurn(agent, proposalPrompt, options, 'proposal', results, `${agent.agent.name}_proposal_r${this.state.round}`);
129
191
  this.turnManager.advance();
130
192
  }
131
193
  this.state.currentTurn = null;
@@ -144,18 +206,11 @@ export class NegotiationStrategy extends BaseStrategy {
144
206
  }
145
207
  const counterPrompt = this.buildCounterPrompt(options.input);
146
208
  for (const agent of agents) {
147
- const offersToAgent = pendingOffers.filter((o) => {
148
- const recipients = Array.isArray(o.to) ? o.to : [o.to];
149
- return recipients.includes(agent.agent.name);
150
- });
209
+ const offersToAgent = this.state.offers.filter((o) => o.status === 'pending' && this.isRecipient(o, agent.agent.name));
151
210
  if (offersToAgent.length === 0)
152
211
  continue;
153
- this.state.currentTurn = agent.agent.name;
154
- this.turnManager.startTurn();
155
- this.updateBlackboard();
156
212
  this.coordinator.events.emit('negotiation:turn', { agent: agent.agent.name, round: this.state.round, phase: 'counter' }, agent.agent.name);
157
- const result = await this.coordinator.runAgent(agent.agent.name, counterPrompt, this.buildAgentContext(agent, options));
158
- results.set(`${agent.agent.name}_counter_r${this.state.round}`, result);
213
+ await this.runTurn(agent, counterPrompt, options, 'counter', results, `${agent.agent.name}_counter_r${this.state.round}`);
159
214
  this.turnManager.advance();
160
215
  }
161
216
  this.state.currentTurn = null;
@@ -164,73 +219,145 @@ export class NegotiationStrategy extends BaseStrategy {
164
219
  }
165
220
  async runRefinementPhase(options, agents, results, suggestion) {
166
221
  this.state.phase = 'refinement';
222
+ const mediationOffer = {
223
+ id: `mediation_${nanoid(10)}`,
224
+ from: MEDIATOR_ID,
225
+ to: agents.map((a) => a.agent.name),
226
+ terms: suggestion.suggestedTerms,
227
+ reasoning: suggestion.rationale,
228
+ timestamp: Date.now(),
229
+ status: 'pending',
230
+ round: this.state.round,
231
+ phase: 'refinement',
232
+ };
233
+ this.state.offers = [
234
+ ...this.state.offers.map((o) => o.from === MEDIATOR_ID && o.status === 'pending'
235
+ ? { ...o, status: 'withdrawn' }
236
+ : o),
237
+ mediationOffer,
238
+ ];
239
+ this.reviewedOfferIds.add(mediationOffer.id);
167
240
  this.updateBlackboard();
168
241
  this.coordinator.events.emit('negotiation:phase-change', { phase: 'refinement', round: this.state.round }, 'system');
169
- const refinementPrompt = this.buildRefinementPrompt(options.input, suggestion);
242
+ const refinementPrompt = this.buildRefinementPrompt(options.input, suggestion, mediationOffer.id);
170
243
  for (const agent of agents) {
171
- this.state.currentTurn = agent.agent.name;
172
- this.updateBlackboard();
173
- const result = await this.coordinator.runAgent(agent.agent.name, refinementPrompt, this.buildAgentContext(agent, options));
174
- results.set(`${agent.agent.name}_refinement_r${this.state.round}`, result);
244
+ await this.runTurn(agent, refinementPrompt, options, 'refinement', results, `${agent.agent.name}_refinement_r${this.state.round}`);
175
245
  }
176
246
  this.state.currentTurn = null;
177
247
  this.state.lastActivityAt = Date.now();
178
248
  this.updateBlackboard();
179
249
  }
180
- async checkForAgreement() {
181
- const currentState = this.coordinator.blackboard.read('negotiation');
182
- if (!currentState)
183
- return false;
184
- this.state = currentState;
185
- const acceptedOffers = this.state.offers.filter((o) => o.status === 'accepted');
186
- if (acceptedOffers.length === 0)
187
- return false;
188
- const latestAccepted = acceptedOffers.sort((a, b) => b.timestamp - a.timestamp)[0];
189
- const allPartiesAccepted = this.checkAllPartiesAccepted(latestAccepted);
190
- if (!allPartiesAccepted)
191
- return false;
192
- const agreement = this.buildAgreement(latestAccepted);
193
- this.state.agreement = agreement;
194
- if (this.approvalIntegration) {
195
- const gate = this.approvalIntegration.shouldTriggerApproval('agreement-reached', this.state, {
196
- agreement,
250
+ /**
251
+ * Pick up changes made by negotiation tools during the last turn and expire stale offers.
252
+ */
253
+ syncState() {
254
+ const latest = readNegotiationState(this.coordinator.blackboard);
255
+ if (latest?.negotiationId === this.state.negotiationId) {
256
+ this.state = {
257
+ ...latest,
258
+ offers: [...latest.offers],
259
+ coalitions: [...latest.coalitions],
260
+ turnHistory: [...latest.turnHistory],
261
+ convergenceHistory: [...latest.convergenceHistory],
262
+ interests: { ...latest.interests },
263
+ };
264
+ }
265
+ const now = Date.now();
266
+ this.state.offers = this.state.offers.map((offer) => {
267
+ if (offer.status !== 'pending' || !isOfferExpired(offer, now))
268
+ return offer;
269
+ this.coordinator.events.emit('negotiation:offer-expired', { offerId: offer.id, from: offer.from }, 'system');
270
+ return { ...offer, status: 'expired' };
271
+ });
272
+ }
273
+ /**
274
+ * Route new offers and newly formed coalitions through the configured approval gates.
275
+ */
276
+ async reviewNewActivity() {
277
+ const newOffers = this.state.offers.filter((o) => !this.reviewedOfferIds.has(o.id));
278
+ const newCoalitions = this.state.coalitions.filter((c) => c.status === 'active' && !this.reviewedCoalitionIds.has(c.id));
279
+ for (const offer of newOffers)
280
+ this.reviewedOfferIds.add(offer.id);
281
+ for (const coalition of newCoalitions)
282
+ this.reviewedCoalitionIds.add(coalition.id);
283
+ if (!this.approvalIntegration)
284
+ return;
285
+ for (const offer of newOffers) {
286
+ const gate = this.approvalIntegration.shouldTriggerApproval('high-value-term', this.state, {
287
+ offer,
197
288
  });
198
- if (gate) {
199
- this.state.phase = 'agreement';
289
+ if (!gate)
290
+ continue;
291
+ const response = await this.approvalIntegration.requestApproval(gate, this.state, this.coordinator.events, { offer });
292
+ if (!response.approved) {
293
+ this.syncState();
294
+ this.state.offers = this.state.offers.map((o) => o.id === offer.id && o.status === 'pending' ? { ...o, status: 'withdrawn' } : o);
200
295
  this.updateBlackboard();
201
- const response = await this.approvalIntegration.requestApproval(gate, this.state, this.coordinator.events, { agreement });
202
- if (!response.approved) {
203
- this.state.agreement = undefined;
204
- if (response.continueNegotiation) {
205
- return false;
296
+ }
297
+ }
298
+ for (const coalition of newCoalitions) {
299
+ const gate = this.approvalIntegration.shouldTriggerApproval('coalition-formed', this.state);
300
+ if (!gate)
301
+ continue;
302
+ const response = await this.approvalIntegration.requestApproval(gate, this.state, this.coordinator.events);
303
+ if (!response.approved) {
304
+ this.syncState();
305
+ this.state.coalitions = this.state.coalitions.map((c) => c.id === coalition.id ? { ...c, status: 'dissolved' } : c);
306
+ this.updateBlackboard();
307
+ this.coordinator.events.emit('negotiation:coalition-dissolved', { coalitionId: coalition.id, reason: 'approval_rejected' }, 'system');
308
+ }
309
+ }
310
+ }
311
+ isRecipient(offer, agentName) {
312
+ return (Array.isArray(offer.to) ? offer.to : [offer.to]).includes(agentName);
313
+ }
314
+ meetsQuorum(offer) {
315
+ const quorum = this.config.quorum;
316
+ if (quorum === undefined || quorum <= 0)
317
+ return true;
318
+ const required = quorum <= 1 ? Math.ceil(quorum * this.agentNames.length) : Math.ceil(quorum);
319
+ return new Set(offerParties(offer)).size >= required;
320
+ }
321
+ async checkForAgreement() {
322
+ const candidates = this.state.offers
323
+ .filter((o) => o.status === 'accepted' && isFullyAccepted(this.state, o))
324
+ .filter((o) => this.meetsQuorum(o))
325
+ .sort((a, b) => b.timestamp - a.timestamp);
326
+ for (const offer of candidates) {
327
+ const agreement = this.buildAgreement(offer);
328
+ if (this.approvalIntegration) {
329
+ const gate = this.approvalIntegration.shouldTriggerApproval('agreement-reached', this.state, { agreement });
330
+ if (gate) {
331
+ this.state.phase = 'agreement';
332
+ this.updateBlackboard();
333
+ const response = await this.approvalIntegration.requestApproval(gate, this.state, this.coordinator.events, { agreement });
334
+ if (!response.approved) {
335
+ this.state.offers = this.state.offers.map((o) => o.id === offer.id ? { ...o, status: 'withdrawn' } : o);
336
+ this.updateBlackboard();
337
+ continue;
206
338
  }
207
- return false;
339
+ agreement.approvalStatus = 'approved';
340
+ agreement.approvalResponse = {
341
+ approvedBy: response.respondedBy,
342
+ approvedAt: response.respondedAt,
343
+ comment: response.comment,
344
+ };
208
345
  }
209
- agreement.approvalStatus = 'approved';
210
- agreement.approvalResponse = {
211
- approvedBy: response.respondedBy,
212
- approvedAt: response.respondedAt,
213
- comment: response.comment,
214
- };
215
346
  }
347
+ this.state.agreement = agreement;
348
+ this.state.phase = 'agreement';
349
+ this.updateBlackboard();
350
+ return true;
216
351
  }
217
- this.coordinator.events.emit('negotiation:agreement-reached', { agreement }, 'system');
218
- return true;
219
- }
220
- checkAllPartiesAccepted(offer) {
221
- const recipients = Array.isArray(offer.to) ? offer.to : [offer.to];
222
- return recipients.every((recipient) => {
223
- const acceptance = this.state.offers.find((o) => o.from === recipient && o.inResponseTo === offer.id && o.status === 'accepted');
224
- return !!acceptance || offer.status === 'accepted';
225
- });
352
+ return false;
226
353
  }
227
354
  buildAgreement(offer) {
228
- const recipients = Array.isArray(offer.to) ? offer.to : [offer.to];
229
- const parties = [offer.from, ...recipients];
230
355
  const relatedOfferIds = [offer.id];
356
+ const visited = new Set(relatedOfferIds);
231
357
  let current = offer;
232
- while (current.inResponseTo) {
358
+ while (current.inResponseTo && !visited.has(current.inResponseTo)) {
233
359
  relatedOfferIds.push(current.inResponseTo);
360
+ visited.add(current.inResponseTo);
234
361
  const parent = this.state.offers.find((o) => o.id === current.inResponseTo);
235
362
  if (!parent)
236
363
  break;
@@ -238,33 +365,32 @@ export class NegotiationStrategy extends BaseStrategy {
238
365
  }
239
366
  return {
240
367
  id: `agreement_${nanoid(12)}`,
241
- parties,
368
+ parties: Array.from(new Set(offerParties(offer))),
242
369
  terms: offer.terms,
243
- reachedVia: 'consensus',
370
+ reachedVia: offer.from === MEDIATOR_ID ? 'compromise' : 'consensus',
244
371
  timestamp: Date.now(),
245
372
  sourceOffers: relatedOfferIds,
246
373
  requiresApproval: !!this.approvalIntegration,
247
374
  };
248
375
  }
249
- isDeadlocked() {
376
+ isDeadlocked(round) {
250
377
  const latestMetrics = this.state.convergenceHistory[this.state.convergenceHistory.length - 1];
251
378
  if (!latestMetrics)
252
379
  return false;
253
- if (latestMetrics.roundsWithoutProgress >= (this.config.maxRoundsWithoutProgress ?? 3)) {
380
+ const mediationFailed = this.lastMediationRound !== undefined && this.lastMediationRound < round;
381
+ if (mediationFailed &&
382
+ latestMetrics.roundsWithoutProgress >= (this.config.maxRoundsWithoutProgress ?? 3)) {
254
383
  return true;
255
384
  }
256
- if (latestMetrics.convergenceTrend === 'declining' && latestMetrics.overallConvergence < 0.2) {
257
- return true;
258
- }
259
- return false;
385
+ return latestMetrics.convergenceTrend === 'declining' && latestMetrics.overallConvergence < 0.2;
260
386
  }
261
- async handleDeadlock(options, agents, results) {
387
+ async handleDeadlock(options, results) {
262
388
  this.state.phase = 'deadlock';
263
389
  this.updateBlackboard();
264
390
  this.coordinator.events.emit('negotiation:deadlock', { round: this.state.round, offers: this.state.offers.length }, 'system');
265
391
  switch (this.config.onDeadlock) {
266
392
  case 'escalate':
267
- return this.escalate(options, agents, results);
393
+ return this.escalate(results);
268
394
  case 'supervisor-decides': {
269
395
  const supervisors = this.coordinator.getAgentsByRole('supervisor');
270
396
  if (supervisors.length > 0) {
@@ -281,7 +407,7 @@ export class NegotiationStrategy extends BaseStrategy {
281
407
  return this.finalizeResult(results, 'deadlock');
282
408
  }
283
409
  }
284
- async escalate(_options, _agents, results) {
410
+ async escalate(results) {
285
411
  this.state.phase = 'escalation';
286
412
  this.updateBlackboard();
287
413
  this.coordinator.events.emit('negotiation:escalation', { reason: 'deadlock', round: this.state.round }, 'system');
@@ -290,19 +416,11 @@ export class NegotiationStrategy extends BaseStrategy {
290
416
  if (gate) {
291
417
  const response = await this.approvalIntegration.requestApproval(gate, this.state, this.coordinator.events);
292
418
  if (response.approved && response.suggestedModifications) {
293
- const escalationOffer = {
294
- id: `escalation_${nanoid(8)}`,
295
- from: 'escalation_authority',
296
- to: Object.keys(this.state.interests),
297
- terms: response.suggestedModifications,
298
- reasoning: 'Terms proposed by escalation authority after deadlock',
299
- timestamp: Date.now(),
300
- status: 'pending',
301
- round: this.state.round + 1,
302
- phase: 'escalation',
303
- };
304
- this.state.offers.push(escalationOffer);
305
- this.updateBlackboard();
419
+ this.coordinator.events.emit('negotiation:escalation', {
420
+ reason: 'authority_modifications',
421
+ suggestedModifications: response.suggestedModifications,
422
+ respondedBy: response.respondedBy,
423
+ }, 'system');
306
424
  }
307
425
  }
308
426
  }
@@ -319,7 +437,9 @@ export class NegotiationStrategy extends BaseStrategy {
319
437
  }
320
438
  majorityRules(results) {
321
439
  const latestOffers = new Map();
322
- for (const offer of this.state.offers) {
440
+ for (const offer of [...this.state.offers].sort((a, b) => a.timestamp - b.timestamp)) {
441
+ if (offer.from === MEDIATOR_ID)
442
+ continue;
323
443
  if (offer.status === 'pending' || offer.status === 'accepted') {
324
444
  latestOffers.set(offer.from, offer);
325
445
  }
@@ -327,10 +447,11 @@ export class NegotiationStrategy extends BaseStrategy {
327
447
  const termVotes = new Map();
328
448
  for (const offer of latestOffers.values()) {
329
449
  for (const term of offer.terms) {
330
- if (!termVotes.has(term.termId)) {
331
- termVotes.set(term.termId, new Map());
450
+ let valueVotes = termVotes.get(term.termId);
451
+ if (!valueVotes) {
452
+ valueVotes = new Map();
453
+ termVotes.set(term.termId, valueVotes);
332
454
  }
333
- const valueVotes = termVotes.get(term.termId);
334
455
  const valueKey = JSON.stringify(term.value);
335
456
  valueVotes.set(valueKey, (valueVotes.get(valueKey) ?? 0) + 1);
336
457
  }
@@ -349,15 +470,12 @@ export class NegotiationStrategy extends BaseStrategy {
349
470
  .flatMap((o) => o.terms)
350
471
  .find((t) => t.termId === termId);
351
472
  if (sampleTerm) {
352
- majorityTerms.push({
353
- ...sampleTerm,
354
- value: winningValue,
355
- });
473
+ majorityTerms.push({ ...sampleTerm, value: winningValue });
356
474
  }
357
475
  }
358
476
  const agreement = {
359
477
  id: `agreement_${nanoid(12)}`,
360
- parties: Array.from(latestOffers.keys()),
478
+ parties: [...this.agentNames],
361
479
  terms: majorityTerms,
362
480
  reachedVia: 'majority',
363
481
  timestamp: Date.now(),
@@ -370,14 +488,14 @@ export class NegotiationStrategy extends BaseStrategy {
370
488
  }
371
489
  arbitrate(results) {
372
490
  this.coordinator.events.emit('negotiation:arbitration', { round: this.state.round }, 'system');
373
- const pendingOffers = this.state.offers.filter((o) => o.status === 'pending' || o.status === 'countered');
374
- if (pendingOffers.length === 0) {
491
+ const openOffers = this.state.offers.filter((o) => o.status === 'pending' || o.status === 'countered');
492
+ if (openOffers.length === 0) {
375
493
  return this.finalizeResult(results, 'deadlock');
376
494
  }
377
- const arbitrationResult = this.performArbitration(pendingOffers);
495
+ const arbitrationResult = this.performArbitration(openOffers);
378
496
  const agreement = {
379
497
  id: `agreement_${nanoid(12)}`,
380
- parties: Object.keys(this.state.interests),
498
+ parties: [...this.agentNames],
381
499
  terms: arbitrationResult.proposal.terms,
382
500
  reachedVia: 'arbitration',
383
501
  timestamp: Date.now(),
@@ -402,10 +520,12 @@ export class NegotiationStrategy extends BaseStrategy {
402
520
  continue;
403
521
  const numericValues = termValues.filter((t) => typeof t.value === 'number');
404
522
  if (numericValues.length > 0) {
405
- const totalWeight = numericValues.reduce((s, t) => s + t.priority, 0);
406
- const weightedAvg = numericValues.reduce((s, t) => s + (t.value * t.priority) / totalWeight, 0);
523
+ const totalWeight = numericValues.reduce((s, t) => s + Math.max(t.priority, 0), 0);
524
+ const weightedAvg = totalWeight > 0
525
+ ? numericValues.reduce((s, t) => s + (t.value * Math.max(t.priority, 0)) / totalWeight, 0)
526
+ : numericValues.reduce((s, t) => s + t.value, 0) / numericValues.length;
407
527
  arbitratedTerms.push({
408
- ...termValues[0],
528
+ ...numericValues[0],
409
529
  value: Math.round(weightedAvg * 100) / 100,
410
530
  negotiable: false,
411
531
  });
@@ -420,7 +540,7 @@ export class NegotiationStrategy extends BaseStrategy {
420
540
  const arbitratedOffer = {
421
541
  id: `arbitration_${nanoid(8)}`,
422
542
  from: 'arbitrator',
423
- to: Object.keys(this.state.interests),
543
+ to: [...this.agentNames],
424
544
  terms: arbitratedTerms,
425
545
  reasoning: 'Terms determined by weighted average arbitration',
426
546
  timestamp: Date.now(),
@@ -447,7 +567,9 @@ export class NegotiationStrategy extends BaseStrategy {
447
567
  rounds: this.state.round,
448
568
  duration: Date.now() - this.state.startedAt,
449
569
  };
450
- this.coordinator.events.emit(outcome === 'agreement' ? 'negotiation:agreement-reached' : 'negotiation:terminated', { result: negotiationResult }, 'system');
570
+ this.coordinator.events.emit(outcome === 'agreement' ? 'negotiation:agreement-reached' : 'negotiation:terminated', outcome === 'agreement'
571
+ ? { agreement: this.state.agreement, result: negotiationResult }
572
+ : { result: negotiationResult }, 'system');
451
573
  const output = this.buildFinalOutput(negotiationResult);
452
574
  return {
453
575
  output,
@@ -457,11 +579,10 @@ export class NegotiationStrategy extends BaseStrategy {
457
579
  }
458
580
  getFinalPositions() {
459
581
  const positions = {};
460
- for (const agentName of Object.keys(this.state.interests)) {
461
- const lastOffer = this.state.offers
582
+ for (const agentName of this.agentNames) {
583
+ positions[agentName] = this.state.offers
462
584
  .filter((o) => o.from === agentName)
463
585
  .sort((a, b) => b.timestamp - a.timestamp)[0];
464
- positions[agentName] = lastOffer;
465
586
  }
466
587
  return positions;
467
588
  }
@@ -499,7 +620,7 @@ export class NegotiationStrategy extends BaseStrategy {
499
620
  return output;
500
621
  }
501
622
  updateBlackboard() {
502
- this.coordinator.blackboard.write('negotiation', this.state, 'system');
623
+ this.coordinator.blackboard.write(NEGOTIATION_SECTION, { ...this.state }, 'system');
503
624
  }
504
625
  buildAgentContext(agent, options) {
505
626
  return {
@@ -559,7 +680,7 @@ Your task is to:
559
680
  Consider carefully which terms you can accept and which need modification. A counter-offer should move toward agreement while protecting your interests.
560
681
  `.trim();
561
682
  }
562
- buildRefinementPrompt(topic, suggestion) {
683
+ buildRefinementPrompt(topic, suggestion, mediationOfferId) {
563
684
  const termsDescription = suggestion.suggestedTerms
564
685
  .map((t) => ` - ${t.label}: ${JSON.stringify(t.value)}`)
565
686
  .join('\n');
@@ -579,10 +700,12 @@ ${termsDescription}
579
700
 
580
701
  Rationale: ${suggestion.rationale}
581
702
 
582
- Please consider this compromise proposal. You may:
583
- 1. Accept the suggested terms using 'accept_offer' if acceptable
584
- 2. Make a counter-proposal using 'counter_offer' with minor modifications
585
- 3. Reject and explain why using 'reject_offer'
703
+ The compromise has been submitted as offer "${mediationOfferId}". You may:
704
+ 1. Accept it using 'accept_offer' with offerId "${mediationOfferId}" if acceptable
705
+ 2. Make a counter-proposal using 'counter_offer' (inResponseTo "${mediationOfferId}") with minor modifications
706
+ 3. Reject it using 'reject_offer' and explain why
707
+
708
+ Agreement is reached when every party accepts the compromise.
586
709
 
587
710
  The goal is to break the deadlock and move toward agreement.
588
711
  `.trim();