@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.
- 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 +89 -61
- 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/assessor/scoring.js +1 -1
- package/dist/assessor/scoring.js.map +1 -1
- package/dist/assessor/task-analyzer.d.ts.map +1 -1
- package/dist/assessor/task-analyzer.js +4 -19
- package/dist/assessor/task-analyzer.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 +80 -41
- package/dist/communication/blackboard.js.map +1 -1
- package/dist/communication/event-emitter.d.ts +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 +12 -5
- package/dist/communication/redis-blackboard.d.ts.map +1 -1
- package/dist/communication/redis-blackboard.js +214 -94
- package/dist/communication/redis-blackboard.js.map +1 -1
- package/dist/communication/redis-event-emitter.d.ts +14 -1
- package/dist/communication/redis-event-emitter.d.ts.map +1 -1
- package/dist/communication/redis-event-emitter.js +107 -47
- package/dist/communication/redis-event-emitter.js.map +1 -1
- package/dist/communication/redis-message-bus.d.ts +13 -6
- package/dist/communication/redis-message-bus.d.ts.map +1 -1
- package/dist/communication/redis-message-bus.js +93 -66
- 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 -336
- package/dist/coordinator.js.map +1 -1
- package/dist/distributed/distributed-coordinator.d.ts +55 -49
- package/dist/distributed/distributed-coordinator.d.ts.map +1 -1
- package/dist/distributed/distributed-coordinator.js +166 -253
- 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 +1 -6
- package/dist/resources/circuit-breaker.d.ts.map +1 -1
- package/dist/resources/circuit-breaker.js +14 -17
- package/dist/resources/circuit-breaker.js.map +1 -1
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js.map +1 -1
- package/dist/resources/tracker.d.ts.map +1 -1
- package/dist/resources/tracker.js +8 -5
- package/dist/resources/tracker.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/auction.js +2 -3
- package/dist/strategies/auction.js.map +1 -1
- package/dist/strategies/consensus.d.ts +12 -0
- package/dist/strategies/consensus.d.ts.map +1 -1
- package/dist/strategies/consensus.js +81 -40
- package/dist/strategies/consensus.js.map +1 -1
- package/dist/strategies/debate.d.ts.map +1 -1
- package/dist/strategies/debate.js +19 -9
- package/dist/strategies/debate.js.map +1 -1
- package/dist/strategies/hierarchical.d.ts.map +1 -1
- package/dist/strategies/hierarchical.js +29 -12
- package/dist/strategies/hierarchical.js.map +1 -1
- package/dist/strategies/index.d.ts +8 -7
- package/dist/strategies/index.d.ts.map +1 -1
- package/dist/strategies/index.js +1 -7
- package/dist/strategies/index.js.map +1 -1
- package/dist/strategies/negotiation/approval.d.ts.map +1 -1
- package/dist/strategies/negotiation/approval.js +47 -21
- package/dist/strategies/negotiation/approval.js.map +1 -1
- package/dist/strategies/negotiation/convergence.js +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 +281 -158
- package/dist/strategies/negotiation-strategy.js.map +1 -1
- package/dist/strategies/pipeline.d.ts.map +1 -1
- package/dist/strategies/pipeline.js +19 -11
- package/dist/strategies/pipeline.js.map +1 -1
- package/dist/swarm.d.ts +24 -15
- package/dist/swarm.d.ts.map +1 -1
- package/dist/swarm.js +193 -116
- 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 -6
- package/dist/tools/blackboard.js.map +1 -1
- package/dist/tools/delegation.d.ts +23 -4
- package/dist/tools/delegation.d.ts.map +1 -1
- package/dist/tools/delegation.js +116 -85
- 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 +19 -4
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/messaging.d.ts +36 -7
- package/dist/tools/messaging.d.ts.map +1 -1
- package/dist/tools/messaging.js +122 -46
- 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 +135 -85
- 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 +56 -71
- 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
|
+
];
|
|
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
|
-
|
|
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,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.
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
257
|
-
return true;
|
|
258
|
-
}
|
|
259
|
-
return false;
|
|
385
|
+
return latestMetrics.convergenceTrend === 'declining' && latestMetrics.overallConvergence < 0.2;
|
|
260
386
|
}
|
|
261
|
-
async handleDeadlock(options,
|
|
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(
|
|
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(
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
331
|
-
|
|
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:
|
|
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
|
|
374
|
-
if (
|
|
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(
|
|
495
|
+
const arbitrationResult = this.performArbitration(openOffers);
|
|
378
496
|
const agreement = {
|
|
379
497
|
id: `agreement_${nanoid(12)}`,
|
|
380
|
-
parties:
|
|
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 =
|
|
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
|
-
...
|
|
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:
|
|
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',
|
|
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
|
|
461
|
-
|
|
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(
|
|
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
|
-
|
|
583
|
-
1. Accept
|
|
584
|
-
2. Make a counter-proposal using 'counter_offer' with minor modifications
|
|
585
|
-
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.
|
|
586
709
|
|
|
587
710
|
The goal is to break the deadlock and move toward agreement.
|
|
588
711
|
`.trim();
|