@cogitator-ai/swarms 0.4.20 → 0.6.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 (140) 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 +72 -53
  9. package/dist/assessor/assessor.js.map +1 -1
  10. package/dist/assessor/model-discovery.d.ts.map +1 -1
  11. package/dist/assessor/model-discovery.js +40 -46
  12. package/dist/assessor/model-discovery.js.map +1 -1
  13. package/dist/assessor/role-matcher.d.ts +6 -24
  14. package/dist/assessor/role-matcher.d.ts.map +1 -1
  15. package/dist/assessor/role-matcher.js +9 -37
  16. package/dist/assessor/role-matcher.js.map +1 -1
  17. package/dist/assessor/scoring.d.ts.map +1 -1
  18. package/dist/assessor/scoring.js +23 -1
  19. package/dist/assessor/scoring.js.map +1 -1
  20. package/dist/base-coordinator.d.ts +117 -0
  21. package/dist/base-coordinator.d.ts.map +1 -0
  22. package/dist/base-coordinator.js +455 -0
  23. package/dist/base-coordinator.js.map +1 -0
  24. package/dist/communication/blackboard.d.ts +34 -4
  25. package/dist/communication/blackboard.d.ts.map +1 -1
  26. package/dist/communication/blackboard.js +81 -46
  27. package/dist/communication/blackboard.js.map +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 +11 -5
  40. package/dist/communication/redis-blackboard.d.ts.map +1 -1
  41. package/dist/communication/redis-blackboard.js +159 -116
  42. package/dist/communication/redis-blackboard.js.map +1 -1
  43. package/dist/communication/redis-event-emitter.d.ts +12 -0
  44. package/dist/communication/redis-event-emitter.d.ts.map +1 -1
  45. package/dist/communication/redis-event-emitter.js +96 -54
  46. package/dist/communication/redis-event-emitter.js.map +1 -1
  47. package/dist/communication/redis-message-bus.d.ts +11 -6
  48. package/dist/communication/redis-message-bus.d.ts.map +1 -1
  49. package/dist/communication/redis-message-bus.js +88 -77
  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 -340
  54. package/dist/coordinator.js.map +1 -1
  55. package/dist/distributed/distributed-coordinator.d.ts +54 -51
  56. package/dist/distributed/distributed-coordinator.d.ts.map +1 -1
  57. package/dist/distributed/distributed-coordinator.js +166 -284
  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.map +1 -1
  68. package/dist/resources/circuit-breaker.js +3 -4
  69. package/dist/resources/circuit-breaker.js.map +1 -1
  70. package/dist/shared/hierarchy.d.ts +21 -0
  71. package/dist/shared/hierarchy.d.ts.map +1 -0
  72. package/dist/shared/hierarchy.js +27 -0
  73. package/dist/shared/hierarchy.js.map +1 -0
  74. package/dist/shared/negotiation.d.ts +27 -0
  75. package/dist/shared/negotiation.d.ts.map +1 -0
  76. package/dist/shared/negotiation.js +55 -0
  77. package/dist/shared/negotiation.js.map +1 -0
  78. package/dist/shared/voting.d.ts +6 -0
  79. package/dist/shared/voting.d.ts.map +1 -0
  80. package/dist/shared/voting.js +13 -0
  81. package/dist/shared/voting.js.map +1 -0
  82. package/dist/strategies/consensus.d.ts +12 -0
  83. package/dist/strategies/consensus.d.ts.map +1 -1
  84. package/dist/strategies/consensus.js +75 -47
  85. package/dist/strategies/consensus.js.map +1 -1
  86. package/dist/strategies/debate.d.ts.map +1 -1
  87. package/dist/strategies/debate.js +18 -8
  88. package/dist/strategies/debate.js.map +1 -1
  89. package/dist/strategies/hierarchical.d.ts.map +1 -1
  90. package/dist/strategies/hierarchical.js +29 -14
  91. package/dist/strategies/hierarchical.js.map +1 -1
  92. package/dist/strategies/negotiation/approval.d.ts.map +1 -1
  93. package/dist/strategies/negotiation/approval.js +22 -19
  94. package/dist/strategies/negotiation/approval.js.map +1 -1
  95. package/dist/strategies/negotiation-strategy.d.ts +24 -2
  96. package/dist/strategies/negotiation-strategy.d.ts.map +1 -1
  97. package/dist/strategies/negotiation-strategy.js +276 -141
  98. package/dist/strategies/negotiation-strategy.js.map +1 -1
  99. package/dist/strategies/pipeline.js +6 -6
  100. package/dist/strategies/pipeline.js.map +1 -1
  101. package/dist/swarm.d.ts +25 -11
  102. package/dist/swarm.d.ts.map +1 -1
  103. package/dist/swarm.js +195 -121
  104. package/dist/swarm.js.map +1 -1
  105. package/dist/tools/blackboard.d.ts +1 -1
  106. package/dist/tools/blackboard.d.ts.map +1 -1
  107. package/dist/tools/blackboard.js +26 -12
  108. package/dist/tools/blackboard.js.map +1 -1
  109. package/dist/tools/delegation.d.ts +17 -4
  110. package/dist/tools/delegation.d.ts.map +1 -1
  111. package/dist/tools/delegation.js +95 -88
  112. package/dist/tools/delegation.js.map +1 -1
  113. package/dist/tools/index.d.ts +3 -1
  114. package/dist/tools/index.d.ts.map +1 -1
  115. package/dist/tools/index.js +9 -4
  116. package/dist/tools/index.js.map +1 -1
  117. package/dist/tools/messaging.d.ts +38 -6
  118. package/dist/tools/messaging.d.ts.map +1 -1
  119. package/dist/tools/messaging.js +118 -41
  120. package/dist/tools/messaging.js.map +1 -1
  121. package/dist/tools/negotiation.d.ts +8 -4
  122. package/dist/tools/negotiation.d.ts.map +1 -1
  123. package/dist/tools/negotiation.js +138 -115
  124. package/dist/tools/negotiation.js.map +1 -1
  125. package/dist/tools/voting.d.ts +3 -0
  126. package/dist/tools/voting.d.ts.map +1 -1
  127. package/dist/tools/voting.js +13 -12
  128. package/dist/tools/voting.js.map +1 -1
  129. package/dist/utils/concurrency.d.ts +10 -0
  130. package/dist/utils/concurrency.d.ts.map +1 -0
  131. package/dist/utils/concurrency.js +28 -0
  132. package/dist/utils/concurrency.js.map +1 -0
  133. package/dist/utils/invoke.d.ts +2 -0
  134. package/dist/utils/invoke.d.ts.map +1 -0
  135. package/dist/utils/invoke.js +19 -0
  136. package/dist/utils/invoke.js.map +1 -0
  137. package/dist/workflow/swarm-node.d.ts.map +1 -1
  138. package/dist/workflow/swarm-node.js +55 -98
  139. package/dist/workflow/swarm-node.js.map +1 -1
  140. package/package.json +16 -14
@@ -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
+ ];
97
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);
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,69 +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 acceptedOffers = this.state.offers.filter((o) => o.status === 'accepted');
182
- if (acceptedOffers.length === 0)
183
- return false;
184
- const latestAccepted = acceptedOffers.sort((a, b) => b.timestamp - a.timestamp)[0];
185
- const allPartiesAccepted = this.checkAllPartiesAccepted(latestAccepted);
186
- if (!allPartiesAccepted)
187
- return false;
188
- const agreement = this.buildAgreement(latestAccepted);
189
- this.state.agreement = agreement;
190
- if (this.approvalIntegration) {
191
- const gate = this.approvalIntegration.shouldTriggerApproval('agreement-reached', this.state, {
192
- 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,
193
288
  });
194
- if (gate) {
195
- 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);
295
+ this.updateBlackboard();
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);
196
306
  this.updateBlackboard();
197
- const response = await this.approvalIntegration.requestApproval(gate, this.state, this.coordinator.events, { agreement });
198
- if (!response.approved) {
199
- this.state.agreement = undefined;
200
- if (response.continueNegotiation) {
201
- return false;
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;
202
338
  }
203
- return false;
339
+ agreement.approvalStatus = 'approved';
340
+ agreement.approvalResponse = {
341
+ approvedBy: response.respondedBy,
342
+ approvedAt: response.respondedAt,
343
+ comment: response.comment,
344
+ };
204
345
  }
205
- agreement.approvalStatus = 'approved';
206
- agreement.approvalResponse = {
207
- approvedBy: response.respondedBy,
208
- approvedAt: response.respondedAt,
209
- comment: response.comment,
210
- };
211
346
  }
347
+ this.state.agreement = agreement;
348
+ this.state.phase = 'agreement';
349
+ this.updateBlackboard();
350
+ return true;
212
351
  }
213
- this.coordinator.events.emit('negotiation:agreement-reached', { agreement }, 'system');
214
- return true;
215
- }
216
- checkAllPartiesAccepted(offer) {
217
- const recipients = Array.isArray(offer.to) ? offer.to : [offer.to];
218
- return recipients.every((recipient) => {
219
- const acceptance = this.state.offers.find((o) => o.from === recipient && o.inResponseTo === offer.id && o.status === 'accepted');
220
- return !!acceptance;
221
- });
352
+ return false;
222
353
  }
223
354
  buildAgreement(offer) {
224
- const recipients = Array.isArray(offer.to) ? offer.to : [offer.to];
225
- const parties = [offer.from, ...recipients];
226
355
  const relatedOfferIds = [offer.id];
356
+ const visited = new Set(relatedOfferIds);
227
357
  let current = offer;
228
- while (current.inResponseTo) {
358
+ while (current.inResponseTo && !visited.has(current.inResponseTo)) {
229
359
  relatedOfferIds.push(current.inResponseTo);
360
+ visited.add(current.inResponseTo);
230
361
  const parent = this.state.offers.find((o) => o.id === current.inResponseTo);
231
362
  if (!parent)
232
363
  break;
@@ -234,33 +365,32 @@ export class NegotiationStrategy extends BaseStrategy {
234
365
  }
235
366
  return {
236
367
  id: `agreement_${nanoid(12)}`,
237
- parties,
368
+ parties: Array.from(new Set(offerParties(offer))),
238
369
  terms: offer.terms,
239
- reachedVia: 'consensus',
370
+ reachedVia: offer.from === MEDIATOR_ID ? 'compromise' : 'consensus',
240
371
  timestamp: Date.now(),
241
372
  sourceOffers: relatedOfferIds,
242
373
  requiresApproval: !!this.approvalIntegration,
243
374
  };
244
375
  }
245
- isDeadlocked() {
376
+ isDeadlocked(round) {
246
377
  const latestMetrics = this.state.convergenceHistory[this.state.convergenceHistory.length - 1];
247
378
  if (!latestMetrics)
248
379
  return false;
249
- 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)) {
250
383
  return true;
251
384
  }
252
- if (latestMetrics.convergenceTrend === 'declining' && latestMetrics.overallConvergence < 0.2) {
253
- return true;
254
- }
255
- return false;
385
+ return latestMetrics.convergenceTrend === 'declining' && latestMetrics.overallConvergence < 0.2;
256
386
  }
257
- async handleDeadlock(options, agents, results) {
387
+ async handleDeadlock(options, results) {
258
388
  this.state.phase = 'deadlock';
259
389
  this.updateBlackboard();
260
390
  this.coordinator.events.emit('negotiation:deadlock', { round: this.state.round, offers: this.state.offers.length }, 'system');
261
391
  switch (this.config.onDeadlock) {
262
392
  case 'escalate':
263
- return this.escalate(options, agents, results);
393
+ return this.escalate(results);
264
394
  case 'supervisor-decides': {
265
395
  const supervisors = this.coordinator.getAgentsByRole('supervisor');
266
396
  if (supervisors.length > 0) {
@@ -277,7 +407,7 @@ export class NegotiationStrategy extends BaseStrategy {
277
407
  return this.finalizeResult(results, 'deadlock');
278
408
  }
279
409
  }
280
- async escalate(_options, _agents, results) {
410
+ async escalate(results) {
281
411
  this.state.phase = 'escalation';
282
412
  this.updateBlackboard();
283
413
  this.coordinator.events.emit('negotiation:escalation', { reason: 'deadlock', round: this.state.round }, 'system');
@@ -307,7 +437,9 @@ export class NegotiationStrategy extends BaseStrategy {
307
437
  }
308
438
  majorityRules(results) {
309
439
  const latestOffers = new Map();
310
- 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;
311
443
  if (offer.status === 'pending' || offer.status === 'accepted') {
312
444
  latestOffers.set(offer.from, offer);
313
445
  }
@@ -315,10 +447,11 @@ export class NegotiationStrategy extends BaseStrategy {
315
447
  const termVotes = new Map();
316
448
  for (const offer of latestOffers.values()) {
317
449
  for (const term of offer.terms) {
318
- if (!termVotes.has(term.termId)) {
319
- 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);
320
454
  }
321
- const valueVotes = termVotes.get(term.termId);
322
455
  const valueKey = JSON.stringify(term.value);
323
456
  valueVotes.set(valueKey, (valueVotes.get(valueKey) ?? 0) + 1);
324
457
  }
@@ -337,15 +470,12 @@ export class NegotiationStrategy extends BaseStrategy {
337
470
  .flatMap((o) => o.terms)
338
471
  .find((t) => t.termId === termId);
339
472
  if (sampleTerm) {
340
- majorityTerms.push({
341
- ...sampleTerm,
342
- value: winningValue,
343
- });
473
+ majorityTerms.push({ ...sampleTerm, value: winningValue });
344
474
  }
345
475
  }
346
476
  const agreement = {
347
477
  id: `agreement_${nanoid(12)}`,
348
- parties: Array.from(latestOffers.keys()),
478
+ parties: [...this.agentNames],
349
479
  terms: majorityTerms,
350
480
  reachedVia: 'majority',
351
481
  timestamp: Date.now(),
@@ -358,14 +488,14 @@ export class NegotiationStrategy extends BaseStrategy {
358
488
  }
359
489
  arbitrate(results) {
360
490
  this.coordinator.events.emit('negotiation:arbitration', { round: this.state.round }, 'system');
361
- const pendingOffers = this.state.offers.filter((o) => o.status === 'pending' || o.status === 'countered');
362
- if (pendingOffers.length === 0) {
491
+ const openOffers = this.state.offers.filter((o) => o.status === 'pending' || o.status === 'countered');
492
+ if (openOffers.length === 0) {
363
493
  return this.finalizeResult(results, 'deadlock');
364
494
  }
365
- const arbitrationResult = this.performArbitration(pendingOffers);
495
+ const arbitrationResult = this.performArbitration(openOffers);
366
496
  const agreement = {
367
497
  id: `agreement_${nanoid(12)}`,
368
- parties: Object.keys(this.state.interests),
498
+ parties: [...this.agentNames],
369
499
  terms: arbitrationResult.proposal.terms,
370
500
  reachedVia: 'arbitration',
371
501
  timestamp: Date.now(),
@@ -390,10 +520,12 @@ export class NegotiationStrategy extends BaseStrategy {
390
520
  continue;
391
521
  const numericValues = termValues.filter((t) => typeof t.value === 'number');
392
522
  if (numericValues.length > 0) {
393
- const totalWeight = numericValues.reduce((s, t) => s + t.priority, 0);
394
- 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;
395
527
  arbitratedTerms.push({
396
- ...termValues[0],
528
+ ...numericValues[0],
397
529
  value: Math.round(weightedAvg * 100) / 100,
398
530
  negotiable: false,
399
531
  });
@@ -408,7 +540,7 @@ export class NegotiationStrategy extends BaseStrategy {
408
540
  const arbitratedOffer = {
409
541
  id: `arbitration_${nanoid(8)}`,
410
542
  from: 'arbitrator',
411
- to: Object.keys(this.state.interests),
543
+ to: [...this.agentNames],
412
544
  terms: arbitratedTerms,
413
545
  reasoning: 'Terms determined by weighted average arbitration',
414
546
  timestamp: Date.now(),
@@ -435,7 +567,9 @@ export class NegotiationStrategy extends BaseStrategy {
435
567
  rounds: this.state.round,
436
568
  duration: Date.now() - this.state.startedAt,
437
569
  };
438
- 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');
439
573
  const output = this.buildFinalOutput(negotiationResult);
440
574
  return {
441
575
  output,
@@ -445,11 +579,10 @@ export class NegotiationStrategy extends BaseStrategy {
445
579
  }
446
580
  getFinalPositions() {
447
581
  const positions = {};
448
- for (const agentName of Object.keys(this.state.interests)) {
449
- const lastOffer = this.state.offers
582
+ for (const agentName of this.agentNames) {
583
+ positions[agentName] = this.state.offers
450
584
  .filter((o) => o.from === agentName)
451
585
  .sort((a, b) => b.timestamp - a.timestamp)[0];
452
- positions[agentName] = lastOffer;
453
586
  }
454
587
  return positions;
455
588
  }
@@ -487,7 +620,7 @@ export class NegotiationStrategy extends BaseStrategy {
487
620
  return output;
488
621
  }
489
622
  updateBlackboard() {
490
- this.coordinator.blackboard.write('negotiation', this.state, 'system');
623
+ this.coordinator.blackboard.write(NEGOTIATION_SECTION, { ...this.state }, 'system');
491
624
  }
492
625
  buildAgentContext(agent, options) {
493
626
  return {
@@ -547,7 +680,7 @@ Your task is to:
547
680
  Consider carefully which terms you can accept and which need modification. A counter-offer should move toward agreement while protecting your interests.
548
681
  `.trim();
549
682
  }
550
- buildRefinementPrompt(topic, suggestion) {
683
+ buildRefinementPrompt(topic, suggestion, mediationOfferId) {
551
684
  const termsDescription = suggestion.suggestedTerms
552
685
  .map((t) => ` - ${t.label}: ${JSON.stringify(t.value)}`)
553
686
  .join('\n');
@@ -567,10 +700,12 @@ ${termsDescription}
567
700
 
568
701
  Rationale: ${suggestion.rationale}
569
702
 
570
- Please consider this compromise proposal. You may:
571
- 1. Accept the suggested terms using 'accept_offer' if acceptable
572
- 2. Make a counter-proposal using 'counter_offer' with minor modifications
573
- 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.
574
709
 
575
710
  The goal is to break the deadlock and move toward agreement.
576
711
  `.trim();