@cogitator-ai/swarms 0.4.20 → 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.
- package/README.md +245 -897
- package/dist/agents.d.ts +13 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +70 -0
- package/dist/agents.js.map +1 -0
- package/dist/assessor/assessor.d.ts +9 -2
- package/dist/assessor/assessor.d.ts.map +1 -1
- package/dist/assessor/assessor.js +71 -52
- package/dist/assessor/assessor.js.map +1 -1
- package/dist/assessor/role-matcher.d.ts +6 -24
- package/dist/assessor/role-matcher.d.ts.map +1 -1
- package/dist/assessor/role-matcher.js +9 -37
- package/dist/assessor/role-matcher.js.map +1 -1
- package/dist/base-coordinator.d.ts +117 -0
- package/dist/base-coordinator.d.ts.map +1 -0
- package/dist/base-coordinator.js +455 -0
- package/dist/base-coordinator.js.map +1 -0
- package/dist/communication/blackboard.d.ts +34 -4
- package/dist/communication/blackboard.d.ts.map +1 -1
- package/dist/communication/blackboard.js +81 -46
- package/dist/communication/blackboard.js.map +1 -1
- package/dist/communication/event-emitter.d.ts.map +1 -1
- package/dist/communication/event-emitter.js +6 -17
- package/dist/communication/event-emitter.js.map +1 -1
- package/dist/communication/index.d.ts +2 -2
- package/dist/communication/index.d.ts.map +1 -1
- package/dist/communication/index.js +2 -2
- package/dist/communication/index.js.map +1 -1
- package/dist/communication/message-bus.d.ts +48 -5
- package/dist/communication/message-bus.d.ts.map +1 -1
- package/dist/communication/message-bus.js +135 -50
- package/dist/communication/message-bus.js.map +1 -1
- package/dist/communication/redis-blackboard.d.ts +11 -5
- package/dist/communication/redis-blackboard.d.ts.map +1 -1
- package/dist/communication/redis-blackboard.js +159 -116
- package/dist/communication/redis-blackboard.js.map +1 -1
- package/dist/communication/redis-event-emitter.d.ts +12 -0
- package/dist/communication/redis-event-emitter.d.ts.map +1 -1
- package/dist/communication/redis-event-emitter.js +96 -54
- package/dist/communication/redis-event-emitter.js.map +1 -1
- package/dist/communication/redis-message-bus.d.ts +11 -6
- package/dist/communication/redis-message-bus.d.ts.map +1 -1
- package/dist/communication/redis-message-bus.js +88 -77
- package/dist/communication/redis-message-bus.js.map +1 -1
- package/dist/coordinator.d.ts +7 -41
- package/dist/coordinator.d.ts.map +1 -1
- package/dist/coordinator.js +17 -340
- package/dist/coordinator.js.map +1 -1
- package/dist/distributed/distributed-coordinator.d.ts +54 -51
- package/dist/distributed/distributed-coordinator.d.ts.map +1 -1
- package/dist/distributed/distributed-coordinator.js +166 -284
- package/dist/distributed/distributed-coordinator.js.map +1 -1
- package/dist/distributed/index.d.ts +1 -1
- package/dist/distributed/index.d.ts.map +1 -1
- package/dist/distributed/index.js +1 -1
- package/dist/distributed/index.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/resources/circuit-breaker.d.ts.map +1 -1
- package/dist/resources/circuit-breaker.js +3 -4
- package/dist/resources/circuit-breaker.js.map +1 -1
- package/dist/shared/hierarchy.d.ts +21 -0
- package/dist/shared/hierarchy.d.ts.map +1 -0
- package/dist/shared/hierarchy.js +27 -0
- package/dist/shared/hierarchy.js.map +1 -0
- package/dist/shared/negotiation.d.ts +27 -0
- package/dist/shared/negotiation.d.ts.map +1 -0
- package/dist/shared/negotiation.js +55 -0
- package/dist/shared/negotiation.js.map +1 -0
- package/dist/shared/voting.d.ts +6 -0
- package/dist/shared/voting.d.ts.map +1 -0
- package/dist/shared/voting.js +13 -0
- package/dist/shared/voting.js.map +1 -0
- package/dist/strategies/consensus.d.ts +12 -0
- package/dist/strategies/consensus.d.ts.map +1 -1
- package/dist/strategies/consensus.js +75 -47
- package/dist/strategies/consensus.js.map +1 -1
- package/dist/strategies/debate.d.ts.map +1 -1
- package/dist/strategies/debate.js +18 -8
- package/dist/strategies/debate.js.map +1 -1
- package/dist/strategies/hierarchical.d.ts.map +1 -1
- package/dist/strategies/hierarchical.js +29 -14
- package/dist/strategies/hierarchical.js.map +1 -1
- package/dist/strategies/negotiation/approval.d.ts.map +1 -1
- package/dist/strategies/negotiation/approval.js +22 -19
- package/dist/strategies/negotiation/approval.js.map +1 -1
- package/dist/strategies/negotiation-strategy.d.ts +24 -2
- package/dist/strategies/negotiation-strategy.d.ts.map +1 -1
- package/dist/strategies/negotiation-strategy.js +276 -141
- package/dist/strategies/negotiation-strategy.js.map +1 -1
- package/dist/strategies/pipeline.js +6 -6
- package/dist/strategies/pipeline.js.map +1 -1
- package/dist/swarm.d.ts +25 -11
- package/dist/swarm.d.ts.map +1 -1
- package/dist/swarm.js +195 -121
- package/dist/swarm.js.map +1 -1
- package/dist/tools/blackboard.d.ts +1 -1
- package/dist/tools/blackboard.d.ts.map +1 -1
- package/dist/tools/blackboard.js +26 -12
- package/dist/tools/blackboard.js.map +1 -1
- package/dist/tools/delegation.d.ts +17 -4
- package/dist/tools/delegation.d.ts.map +1 -1
- package/dist/tools/delegation.js +95 -88
- package/dist/tools/delegation.js.map +1 -1
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +9 -4
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/messaging.d.ts +38 -6
- package/dist/tools/messaging.d.ts.map +1 -1
- package/dist/tools/messaging.js +118 -41
- package/dist/tools/messaging.js.map +1 -1
- package/dist/tools/negotiation.d.ts +8 -4
- package/dist/tools/negotiation.d.ts.map +1 -1
- package/dist/tools/negotiation.js +138 -115
- package/dist/tools/negotiation.js.map +1 -1
- package/dist/tools/voting.d.ts +3 -0
- package/dist/tools/voting.d.ts.map +1 -1
- package/dist/tools/voting.js +13 -12
- package/dist/tools/voting.js.map +1 -1
- package/dist/utils/concurrency.d.ts +10 -0
- package/dist/utils/concurrency.d.ts.map +1 -0
- package/dist/utils/concurrency.js +28 -0
- package/dist/utils/concurrency.js.map +1 -0
- package/dist/utils/invoke.d.ts +2 -0
- package/dist/utils/invoke.d.ts.map +1 -0
- package/dist/utils/invoke.js +19 -0
- package/dist/utils/invoke.js.map +1 -0
- package/dist/workflow/swarm-node.d.ts.map +1 -1
- package/dist/workflow/swarm-node.js +55 -98
- package/dist/workflow/swarm-node.js.map +1 -1
- 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
|
-
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this.
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
this.coordinator.events.emit('negotiation:
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
89
|
+
this.approvalIntegration.submitResponse(requestId, response);
|
|
62
90
|
}
|
|
63
|
-
initializeState(negotiationId
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
this.
|
|
94
|
-
gates: this.config.approvalGates,
|
|
95
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
agent: agent.agent.name,
|
|
109
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
253
|
-
return true;
|
|
254
|
-
}
|
|
255
|
-
return false;
|
|
385
|
+
return latestMetrics.convergenceTrend === 'declining' && latestMetrics.overallConvergence < 0.2;
|
|
256
386
|
}
|
|
257
|
-
async handleDeadlock(options,
|
|
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(
|
|
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(
|
|
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
|
-
|
|
319
|
-
|
|
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:
|
|
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
|
|
362
|
-
if (
|
|
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(
|
|
495
|
+
const arbitrationResult = this.performArbitration(openOffers);
|
|
366
496
|
const agreement = {
|
|
367
497
|
id: `agreement_${nanoid(12)}`,
|
|
368
|
-
parties:
|
|
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 =
|
|
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
|
-
...
|
|
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:
|
|
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',
|
|
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
|
|
449
|
-
|
|
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(
|
|
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
|
-
|
|
571
|
-
1. Accept
|
|
572
|
-
2. Make a counter-proposal using 'counter_offer' with minor modifications
|
|
573
|
-
3. Reject
|
|
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();
|